前幾個月Google開源了它的測試框架,自稱其旗下的上千個項目都在使用它。今天我們就用它來嘗嘗鮮吧?:-)
安裝:
下載Google C++ Testing Framework,解壓...
VC2005:
直接打開msvc\gtest.vcproj或msvc\gtest.sln,直接編譯即可。
Linux/Unix下的GCC:
傳統過程:./configure make
Mingw:
BCC:
用Mingw和BCB6編譯需要修改一些代碼,過幾天我會上傳到www.cppprog.com網站上。
使用:
首先#include <gtest/gtest.h>,當然工程的頭文件路徑要設置正確
1.簡單測試TEST
假如我寫了個函數,是計算階乘的:
1. int Factorial( int n )
2. {
3. if(n==2) return 100;
4. return n<=0? 1 : n*Factorial(n - 1);
5. }
6.
7. TEST(TestFactorial, ZeroInput)
8. {
9. EXPECT_EQ(1, Factorial(0));
10. }
11. TEST(TestFactorial, OtherInput)
12. {
13. EXPECT_EQ(1, Factorial(1));
14. EXPECT_EQ(2, Factorial(2));
15. EXPECT_EQ(6, Factorial(3));
16. EXPECT_EQ(40320, Factorial(8));
17. }
18. int main(int argc, TCHAR* argv[])
19. {
20. testing::InitGoogleTest(&argc,argv);
21. RUN_ALL_TESTS();
22. std::cin.get();
23. return 0;
24. }
25.
運行結果:

瞧:測試框架指出:TestFactorial.ZeroInput運行OK,運行OtherInput時出現三次結果和預期不符。
2.多個測試場景需要相同數據配置的情況,用TEST_F
1.
2. typedef std::basic_string<TCHAR> tstring;
3. struct FooTest : testing::Test {
4.
5. tstring strExe;
6.
7. FooTest() {}
8. virtual ~FooTest() {}
9.
10. virtual void SetUp() {
11.
12. strExe.resize(MAX_PATH);
13. GetModuleFileName(NULL, &strExe[0], MAX_PATH);
14. }
15. virtual void TearDown() { }
16. };
17. tstring getfilename(const tstring &full)
18. {
19. return full.substr(full.rfind(_T('\\')));
20. }
21. tstring getpath(const tstring &full)
22. {
23. return full.substr(0, full.rfind(_T('\\')));
24. }
25. TEST_F(FooTest, Test_GFN)
26. {
27. EXPECT_STREQ(_T("Project1.exe"), getfilename(strExe).c_str());
28. }
29. TEST_F(FooTest, Test_GP)
30. {
31. EXPECT_STREQ(_T("D:\\Code\\libs\\google\\gtest-1.2.1\\BCC_SPC\\bcc\\ex"), getpath(strExe).c_str());
32. }
33. int main(int argc, TCHAR* argv[])
34. {
35. testing::InitGoogleTest(&argc,argv);
36. RUN_ALL_TESTS();
37. std::cin.get();
38. return 0;
39. }
運行結果:

瞧,Google C++ 測試框架毫不客氣地指出偶的getfilename返回的字符串比預期的多了一個'\\'
快速入門:
Google提供了兩種斷言形式,一種以ASSERT_開頭,另一種以EXPECT_開頭,它們的區別是ASSERT_*一旦失敗立馬退出,而EXPECT_還能繼續下去。
斷言列表:
真假條件測試:
致命斷言 | 非致命斷言 | 驗證條件 |
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition為真 |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition 為假 |
數據對比測試:
致命斷言 | 非致命斷言 | 驗證條件 |
ASSERT_EQ(期望值, 實際值); | EXPECT_EQ(期望值, 實際值); | 期望值 == 實際值 |
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 |
字符串(針對C形式的字符串,即char*或wchar_t*)對比測試:
致命斷言 | 非致命斷言 | 驗證條件 |
ASSERT_STREQ(expected_str, actual_str); | EXPECT_STREQ(expected_str, actual_str); | 兩個C字符串有相同的內容 |
ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | 兩個C字符串有不同的內容 |
ASSERT_STRCASEEQ(expected_str, actual_str); | EXPECT_STRCASEEQ(expected_str, actual_str); | 兩個C字符串有相同的內容,忽略大小寫 |
ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | 兩個C字符串有不同的內容,忽略大小寫 |
TEST宏:
TEST宏的作用是創建一個簡單測試,它定義了一個測試函數,在這個函數里可以使用任何C++代碼并使用上面提供的斷言來進行檢查。
TEST的第一個參數是測試用例名,第二個參數是測試用例中某項測試的名稱。一個測試用例可以包含任意數量的獨立測試。這兩個參數組成了一個測試的全稱。
就前面的例子來說:
我們要測試這個函數:int Factorial(int n); // 返回n的階乘
我們的測試用例是:測試輸入0的情況,測試輸入其它數據的情況,于是就有了:
1. TEST(TestFactorial, ZeroInput)
2. {
3. EXPECT_EQ(1, Factorial(0));
4. }
5. TEST(TestFactorial, OtherInput)
6. {
7. EXPECT_EQ(1, Factorial(1));
8. EXPECT_EQ(2, Factorial(2));
9. EXPECT_EQ(6, Factorial(3));
10. EXPECT_EQ(40320, Factorial(8));
11. }
Google Test根據測試用例來分組收集測試結果,因此,邏輯相關的測試應該在同一測試用例中;換句話說,它們的TEST()的第一個參數應該是一樣的。在上面的例子中,我們有兩個測試,ZeroInput和OtherInput,它們都屬于同一個測試用例TestFactorial。
TEST_F宏:
TEST_F宏用于在多個測試中使用同樣的數據配置,所以它又叫:測試夾具(Test Fixtures)
如果我們的多個測試要使用相同的數據(如前例中,我們的Test_GFN和Test_GP都使用程序自身的完整文件名來測試),就可以采用一個測試夾具。
要創建測試固件,只需:
1. 創建一個類繼承自testing::Test。將其中的成員聲明為protected:或是public:,因為我們想要從子類中存取夾具成員。
2. 在該類中聲明測試中所要使用到的數據。
3. 如果需要,編寫一個默認構造函數或者SetUp()函數來為每個測試準備對象。
4. 如果需要,編寫一個析構函數或者TearDown()函數來釋放你在SetUp()函數中申請的資源。
5. 如果需要,定義你的測試所需要共享的子程序。
當我們要使用固件時,使用TEST_F()替換掉TEST(),它允許我們存取測試固件中的對象和子程序:
TEST_F(test_case_name, test_name) {
... test body ...
}
與TEST()一樣,第一個參數是測試用例的名稱,但對TEST_F()來說,這個名稱必須與測試夾具類的名稱一樣。
對于TEST_F()中定義的每個測試,Google Test將會:
1. 創建一個全新的測試夾具
2. 通過SetUp()初始化它,
3. 運行測試
4. 調用TearDown()來進行清理工作
5. 刪除測試夾具。
注意,同一測試用例中,不同的測試擁有不同的測試夾具。Google Test不會對多個測試重用一個測試夾具,測試對測試夾具的改動并不會影響到其他測試。
調用測試
TEST()和TEST_F()向Google Test隱式注冊它們的測試。因此,與很多其他的C++測試框架不同,你不需要為了運行你定義的測試而將它們全部再列出來一次。
在定義好測試后,你可以通過RUN_ALL_TESTS()來運行它們,如果所有測試成功,該函數返回0,否則會返回1.注意RUN_ALL_TESTS()會運行你鏈接到的所有測試——它們可以來自不同的測試用例,甚至是來自不同的文件。
當被調用時,RUN_ALL_TESTS()宏會:
1. 保存所有的Google Test標志。
2. 為一個測試創建測試夾具對象。
3. 調用SetUp()初始化它。
4. 在固件對象上運行測試。
5. 調用TearDown()清理夾具。
6. 刪除固件。
7. 恢復所有Google Test標志的狀態。
8. 重復上訴步驟,直到所有測試完成。
此外,如果第二步時,測試夾具的構造函數產生一個致命錯誤,繼續執行3至5部顯然沒有必要,所以它們會被跳過。與之相似,如果第3部產生致命錯誤,第4部也會被跳過。
重要:你不能忽略掉RUN_ALL_TESTS()的返回值,否則gcc會報一個編譯錯誤。這樣設計的理由是自動化測試服務會根據測試退出返回碼來決定一個測試是否通過,而不是根據其stdout/stderr輸出;因此你的main()函數必須返回RUN_ALL_TESTS()的值。
而且,你應該只調用RUN_ALL_TESTS()一次。多次調用該函數會與Google Test的一些高階特性(如線程安全死亡測試thread-safe death tests)沖突,因而是不被支持的。
編寫main()函數
你可以從下面這個模板開始:
1. #include "this/package/foo.h"
2. #include <gtest/gtest.h>
3. namespace {
4.
5. class FooTest : public testing::Test {
6. protected:
7.
8.
9. FooTest() {
10.
11. }
12. virtual ~FooTest() {
13.
14. }
15.
16.
17. virtual void SetUp() {
18.
19.
20. }
21. virtual void TearDown() {
22.
23.
24. }
25.
26. };
27.
28. TEST_F(FooTest, MethodBarDoesAbc) {
29. const string input_filepath = "this/package/testdata/myinputfile.dat";
30. const string output_filepath = "this/package/testdata/myoutputfile.dat";
31. Foo f;
32. EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
33. }
34.
35. TEST_F(FooTest, DoesXyz) {
36.
37. }
38. }
39. int main(int argc, char **argv) {
40. testing::InitGoogleTest(&argc, argv);
41. return RUN_ALL_TESTS();
42. }
testing::InitGoogleTest() 函數負責解析命令行傳入的Google Test標志,并刪除所有它可以處理的標志。這使得用戶可以通過各種不同的標志控制一個測試程序的行為。關于這一點我們會在GTestAdvanced中講到。你必須在調用RUN_ALL_TESTS()之前調用該函數,否則就無法正確地初始化標示。
在Windows上InitGoogleTest()可以支持寬字符串,所以它也可以被用在以UNICODE模式編譯的程序中。
Google test for mingw 下載: http://www.cppprog.com/2009/0101/26.html Google test for bcb 下載: http://www.cppprog.com/2009/0101/27.html
發表于 @ 2008年12月30日 19:46:00|評論(13 )|收藏
您的文章已經被推薦到CSDN首頁專家專欄欄目,將被更多的CSDN網友閱讀與分享。感謝您對CSDN博客的支持。
wslgz 發表于2008年12月31日 15:37:49 IP:舉報
不知道能不能給個vc的例子,有些地方不是很明白
wslgz 發表于2008年12月31日 15:37:58 IP:舉報
不知道能不能給個vc的例子,有些地方不是很明白
wslgz 發表于2008年12月31日 15:38:31 IP:舉報
不知道能不能給個vc的例子,有些地方不是很明白
同求vc的例子,最好6.0,^_^
同求vc的例子,最好6.0,^_^
同求vc的例子,最好6.0,^_^
VC是最簡單的,從官網上下載以后里面就自帶了VC的工程文件(包含例子和庫文件),直接打開編譯就可以了。
注意庫文件默認是“多線程”而不是“多線程DLL”。
我上面的代碼就是在VC2005里修改的。
至于VC6.0,我沒試過,不過可以考慮去下載我修改的for BCB6.0的代碼,VC6.0應該也能編譯通過。
olay105 發表于2009年1月1日 18:29:28 IP:
總共15個,你知道怎么解決呢? 文章鏈接:http://blog.csdn.net/Waiting4you/archive/2008/12/30/3652350.aspx 發表時間:2009年1月1日 18:29:28">舉報
博主,我是第一次用vs2005,但是在實驗你的文章中出現了很多問題,解決了一些,最后還是有一個棘手的,能幫我解決一下嗎?可能是編譯時的鏈接問題:error LNK2019: unresolved external symbol "protected: __thiscall testing::Test::Test(void)" (??0Test@testing@@IAE@XZ) referenced in function "public: __thiscall TestFactorial_ZeroInput_Test::TestFactorial_ZeroInput_Test(void)" (??0TestFactorial_ZeroInput_Test@@QAE@XZ) project1.obj
總共15個,你知道怎么解決呢?
回olay105:
先打開msvc\gtest.sln,編譯,生成Debug\gtestd.lib和Release\gtest.lib,這是庫文件。
把這個gtest.lib或gtestd.lib加入到這個“出現很多問題”的工程里。注意,因為庫文件工程里默認是“運行時庫:多線程”,所以你的工程也要這么設置。
謝謝博主,我已經解決問題了,很開心!
樓主:
我直接使用VC2005編譯的話也是有很多錯誤,
錯誤 13 未能刪除文件“c:\code\googletest\msvc\Release\vc80.idb”。
請確保該文件未被其他進程打開并且未被寫保護。 gtest_output_test_
不知道是什么原因呢?我的環境里面還安裝了vc2003
謝謝。
樓主:
我直接使用VC2005編譯的話也是有很多錯誤,
錯誤 13 未能刪除文件“c:\code\googletest\msvc\Release\vc80.idb”。
請確保該文件未被其他進程打開并且未被寫保護。 gtest_output_test_
不知道是什么原因呢?我的環境里面還安裝了vc2003
謝謝。