全文分紅三部分:第一部分介紹GoogleTest框架;第二部分是測試;第三部分是我的感想。node
繼續吐槽博客園的Markdown。。不如其餘博客的好用,格式很容易亂。。。。c++
終於調整好格式了。。。。。。博客園的markdown 好low low low啊git
首先,給出官方文檔的連接。如下內容主要是我本身翻譯的官方文檔並結合本身的理解,總結出的基本要點。若是想要對技術更深刻的瞭解,建議仍是去看官方文檔。最後說明一下,這裏的內容僅僅在Linux系統的上執行過,其餘的系統應該也是按照相似的步驟進行。Linux下關於如何安裝GoogleTest框架,請參考我在CSDN的這篇博客github
使用GoogleTest要先從學習寫斷言開始,斷言用於檢測一個測試條件是否爲真。斷言的結果有三種狀況:success, nonfatal failure, fatal failure。若是 fatal failure出現,它將會打斷當前的函數;不然程序會正常運行。
一個測試實例能夠包含多個測試,咱們須要把這些測試組織成合理的結構。當多個測試實例須要共享公共對象和或者子程序,咱們能夠把他們組織到一個測試類中。算法
GoogleTest的斷言是一種相似於函數調用的斷言機制。咱們能夠在GoogleTest自己的斷言消息後面定義本身的測試失敗信息。下面說明幾種不一樣斷言方式:數組
ASSERT_*
產生fatal failures
,直接終止當前函數的運行EXPECT_*
: 產生nonfatal failures
,不會終止當前函數運行EXPECT_*
: 最經常使用的一種方式,能夠容許報告產生一個或者多個failer
爲了提供自定義的失敗信息,可使用C++的stream流操做把字符輸入到斷言中,藉助於<<
操做符號。
好比:markdown
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; for (int i = 0; i < x.size(); ++i) { EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i; }
注意,任何能夠寫入流ostream
的,均可被寫入斷言宏,好比C的字符串、string對象,甚至能夠是廣義上的字符(流):Windows的Unicode下的wchar_t、 TCHAR或者C++的std::string
。全部流的輸入都會轉化成UTF-8
的格式。框架
Name | Academy | score |
---|---|---|
ASSERT_TRUE(condition) | EXPECT_TRUE(condition) | condition is true |
ASSERT_FALSE(condition) | EXPECT_FALSE(condition) | condition is false |
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_EQ(val1,val2); | EXPECT_EQ(val1,val2); | val1 == val2 |
ASSERT_NE(val1,val2); | EXPECT_NE(val1,val2); | val1 != val2 |
ASSERT_LT(val1,val2); | EXPECT_LT(val1,val2); | val1 < val2 |
ASSERT_LE(val1,val2); | EXPECT_LE(val1,val2); | val1 <= val2 |
ASSERT_GT(val1,val2); | EXPECT_GT(val1,val2); | val1 > val2 |
ASSERT_GE(val1,val2); | EXPECT_GE(val1,val2); | val1 >= val2 |
若是上述的一個測試失敗,那麼會打印出val1
和val2
的值。ide
Value參數必須與斷言比較運算符兼容,不然有編譯錯誤。這些斷言機制可使用用戶自定義的結構,可是必須進行運算符重載 。若是使用了自定義結構,咱們最好使用ASSERT_*()
,這樣不只會輸出比較結果,並且會輸出操做數。函數
ASSERT_EQ()
會進行指針比較, 若是使用C風格字符串,那麼比較的是地址!!若是要比較值的話,使用ASSERT_STREQ()
, 若是判斷C字符串是不是NULL
,使用ASSERT_STREQ(NULL, c_string)
。 若是比較string
,那麼使用ASSERT_EQ
。
在這裏,比較的是C風格的字符串,若是想要比較string
對象,請使用EXPECT_EQ
、 EXPECT_NE
等,而不是下面的。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,str2); | the two C strings have the same content |
ASSERT_STRNE(str1,str2); | EXPECT_STRNE(str1,str2); | the two C strings have different content |
ASSERT_STRCASEEQ(str1,str2); | EXPECT_STRCASEEQ(str1,str2); | the two C strings have the same content, ignoring case |
ASSERT_STRCASENE(str1,str2); | EXPECT_STRCASENE(str1,str2); | the two C strings have different content, ignoring case |
建立一個test:
TEST()
宏定義和命名一個特是函數,這是一個普通的無返回值的C++函數。TEST(testCaseName, testName) { ... test body ... }
testCaseName
是 test case的名字,testName是test case內部測試的名稱。二者的名稱必須是合法的C++標識符,不容許有下劃線( _ ) 。不一樣的test case的內部測試能夠有相同的獨立的名字。
int Factorial(int n); // Returns the factorial of n // Tests factorial of 0. TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(1, Factorial(0)); } // Tests factorial of positive numbers. TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(1, Factorial(1)); EXPECT_EQ(2, Factorial(2)); EXPECT_EQ(6, Factorial(3)); EXPECT_EQ(40320, Factorial(8)); }
GoogleTest經過test case組織測試結果,所以邏輯相關的測試必須在一個test case中;換句話說,TEST()
的第一個參數必須相同。上面例子的HandlesZeroInput
和HandlesPositiveInput
都屬於FactorialTest
這個測試實例。
Test Fixture容許咱們使用相同對象配置進行不一樣的測試。
具體步驟:
::testing::Tes
派生一個類。使用是public:
或者protected:
,由於咱們想要在子類中獲取fixture membersSetUp()
函數爲每一個測試準備測試對象。TearDown()
函數來釋放構造函數或者SetUp()
所申請的資源。 想要理解在何時使用SetUp()
和TearDown()
函數, 閱讀這篇文章.當使用一個fixture時,應該使用TEST_F()
而不是TEST()
,由於前者會讓咱們獲取一個test fixture的對象或者子程序。好比:
TEST_F(test_case_name, test_name) { ... test body ... }
與TEST()
相似,第一個參數是test case的名字。可是第二參數必須是test fixture類的名字。
C++的宏系統不容許咱們建立一個單獨的宏來處理各類類型的test。這樣作會有編譯錯誤。
對於每一個使用TEST_F()
定義的測試,Google Test會:
SetUp()
馬上進行初始化TearDown()
函數清理好比,如今編寫一個FIFO隊列的測試,隊列的實現方式:
template <typename E> // E is the element type. class Queue { public: Queue(); void Enqueue(const E& element); E* Dequeue(); // Returns NULL if the queue is empty. size_t size() const; ... };
首先,定義一個fixture類。假設該類的名稱爲Foo
, 按照慣例,咱們應該給fixture命名爲FooTest
class QueueTest : public ::testing::Test { protected: virtual void SetUp() { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // virtual void TearDown() {} Queue<int> q0_; Queue<int> q1_; Queue<int> q2_; };
本例子中,沒用TearDown()
函數,由於沒有資源須要釋放。
如今 ,使用TEST_F()
測試這個fixture:
// 用於測試是不是空隊列 TEST_F(QueueTest, IsEmptyInitially) { EXPECT_EQ(0, q0_.size()); } // 測試出隊的工做狀態 TEST_F(QueueTest, DequeueWorks) { int* n = q0_.Dequeue(); EXPECT_EQ(NULL, n); // 判斷相等的狀況 n = q1_.Dequeue(); ASSERT_TRUE(n != NULL); EXPECT_EQ(1, *n); EXPECT_EQ(0, q1_.size()); delete n; n = q2_.Dequeue(); ASSERT_TRUE(n != NULL); EXPECT_EQ(2, *n); EXPECT_EQ(1, q2_.size()); delete n; }
ASSERT_
與EXPECT_
的區別在前面的文章提到了,這裏不在贅述。
以上面的例子,說明GoogleTest的測試步驟:
QueueTest
對象, 咱們稱之爲t1
t1.SetUp()
初始化t1
。t1
進行第一個測試IsEmptyInitially
測試完成後
, t1.TearDown()
清理全部的資源。t1
析構。QueueTest
對象上執行。(DequeueWorks
開始執行上述步驟)TEST()
與TEST_F()
會跟隨Google自動進行註冊,若是想要執行,咱們不須要從新列出全部定義的測試。
在定義測試以後,使用RUN_ALL_TEST()
。若是測試成功則返回0,不然返回0.,執行這個語句的時候,全部的連接單元都會被測試,它們能夠是不一樣的test case的。
通常步驟:
SetUp()
函數初始化testTearDown()
釋放資源注意,若是構造函數在第2步產生了fatal failure,那麼3-5步會自動跳過。一樣的,3產生了fatal failure,第4步跳過。
注意: 咱們必須返回RUN_ALL_TEST()
的值,不然gcc
會給出編譯錯誤。也就是說,主函數必須返回RUN_ALL_TEST()
的值!並且RUN_ALL_TEST()
只能執行一次!!
Google Test的官方文檔給出了一個測試模板:
#include "this/package/foo.h" #include "gtest/gtest.h" namespace { // The fixture for testing class Foo. class FooTest : public ::testing::Test { protected: // You can remove any or all of the following functions if its body // is empty. FooTest() { // You can do set-up work for each test here. } virtual ~FooTest() { // You can do clean-up work that doesn't throw exceptions here. } // If the constructor and destructor are not enough for setting up // and cleaning up each test, you can define the following methods: virtual void SetUp() { // Code here will be called immediately after the constructor (right // before each test). } virtual void TearDown() { // Code here will be called immediately after each test (right // before the destructor). } // Objects declared here can be used by all tests in the test case for Foo. }; // Tests that the Foo::Bar() method does Abc. TEST_F(FooTest, MethodBarDoesAbc) { const string input_filepath = "this/package/testdata/myinputfile.dat"; const string output_filepath = "this/package/testdata/myoutputfile.dat"; Foo f; EXPECT_EQ(0, f.Bar(input_filepath, output_filepath)); } // Tests that Foo does Xyz. TEST_F(FooTest, DoesXyz) { // Exercises the Xyz feature of Foo. } } // namespace int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
::testing::InitGoogleTest()
用於解析Google Test flags的命令行,而且一處全部已經被識別的標誌,具體的用法請參照這個文檔
由於本人不用vc++,所以各位用VC的dalao就本身看官方文檔吧。。。。。。
以最大子數組線性時間求和問題爲例,介紹GoogleTest的測試框架。給出假期刷題時PAT1007原題目連接,並附上AC的代碼,算法的原理就不在贅述了:
#include <bits/stdc++.h> using namespace std; int num[10005], N; int main() { cin >> N; bool flag = true; for(int i = 0; i < N; ++i) { cin >> num[i]; if(num[i] >= 0) { flag = false; } } // 注意sum初始化要小於0 int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0; while(tmp_b < N) { tmp_sum += num[tmp_b]; if(tmp_sum > sum) { // 更替區間範圍 sum = tmp_sum; a = tmp_a; b = tmp_b; } if(tmp_sum < 0) { // 從新開始起點 tmp_sum = 0; tmp_a = tmp_b + 1; } ++tmp_b; } if(flag) { cout << 0 << " " << num[0] << " " << num[N - 1]; } else { cout << sum << " " << num[a] << " " << num[b]; } return 0; }
可是,上述代碼很差直接測試,所以,把核心功能分離出來寫成函數,同時自定義結構體做爲函數的返回值。改進後的代碼以下:
實際可能出現的狀況有下面幾種:
本次測試代碼內容較少,就當作是熟悉環境的練習了。。。。。。。。。。。。。。。
代碼的Github地址:https://github.com/StudentErick/software_ware_homework
#include <gtest/gtest.h> class Node { public: Node(): sum(0), l(0), r(0) {} // 初始化 int sum, l, r; // 區間和、左右範圍,從0開始 }; Node maxFun(int arr[], int N, bool flag) { Node node; int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0; while(tmp_b < N) { tmp_sum += arr[tmp_b]; if(tmp_sum > sum) { // 更替區間範圍 sum = tmp_sum; a = tmp_a; b = tmp_b; } if(tmp_sum < 0) { // 從新開始起點 tmp_sum = 0; tmp_a = tmp_b + 1; } ++tmp_b; } if(flag) { node.l = 0; node.r = 9; node.sum = 0; } else { node.l = a; node.r = b; node.sum = sum; } return node; } // 須要全局重載 bool operator==(Node a, Node b) { return a.sum == b.sum && a.l == b.l && a.r == b.r; } // 全是負數的測試狀況 int num1[10] = { -1, -2, -5, -2, -8, -6, -9, -3, -10, -4}; // 只有一個最大子數組,左右範圍是2 7,和是25 int num2[10] = {1, -14, 5, 6, 8, 3, -1, 4, -10, 4}; // 有多個,在這裏用兩個測試,左右範圍應該是2 4 和是19 int num3[10] = {1, -14, 5, 6, 8, -300, 5, 6, 8, -4}; TEST(MYTEST, IsOk) { Node n1, n2, n3; n1.l = 0; n1.r = 9; n1.sum = 0; n2.l = 2; n2.r = 7; n2.sum = 25; n3.l = 2; n3.r = 4; n3.sum = 19; ASSERT_EQ(n1, maxFun(num1, 10, true)); ASSERT_EQ(n2, maxFun(num2, 10, false)); ASSERT_EQ(n3, maxFun(num3, 10, false)); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
因爲是自定義的返回值,因此根據官方文檔的建議,使用ASSERT_*
。測試經過結果以下:
假設更改n3
的l
屬性爲4,那麼測試失敗的結果以下:
其中,有相應的不匹配提示。因爲本測試比較簡單,因此沒有使用到test fixture的技術。若是須要,直接套用模板便可。更高級的功能能夠參考
本次測試僅經過官方文檔進行學習。官方的參考文檔是最佳的參考資料。尤爲是對於咱們不熟悉的技術領域,更應該經過閱讀有關文檔進行學習。英文應該做爲技術開發人員的一項基本能力,不只僅是爲了考研或者是所謂的四六級。不少最新的資料或者比較高端的技術或者一些頂級期刊的論文等,幾乎沒有中文版的,所以咱們更應該不斷提升本身的英文水平。同時,要懷着積極的心態去擁抱新的技術和變化,善於利用工具提升開發或者測試效率。