如何用googletest寫單元測試

工程師WWW發表於2016-09-23

googletest是一個用來寫C++單元測試的框架,它是跨平臺的,可應用在windows、linux、Mac等OS平臺上。下面,我來說明如何使用最新的1.6版本gtest寫自己的單元測試。

本文包括以下幾部分:1、獲取並編譯googletest(以下簡稱為gtest);2、如何編寫單元測試用例;3、如何執行單元測試。4、google test內部是如何執行我們的單元測試用例的。


1. 獲取並編譯gtest

gtest試圖跨平臺,理論上,它就應該提供多個版本的binary包。但事實上,gtest只提供原始碼和相應平臺的編譯方式,這是為什麼呢?google的解釋是,我們在編譯出gtest時,有些獨特的工程很可能希望在編譯時加許多flag,把編譯的過程下放給使用者,可以讓使用者更靈活的處理。這個仁者見仁吧,反正也是免費的BSD許可權。


原始碼的獲取地址:http://code.google.com/p/googletest/downloads/list


目前gtest提供的是1.6.0版本,我們看看與以往版本1.5.0的區別:

  1. Changes for 1.6.0:  
  2.   
  3. * New feature: ADD_FAILURE_AT() for reporting a test failure at the  
  4.   given source location -- useful for writing testing utilities.  
  5. 。。。 。。。  
  6. * Bug fixes and implementation clean-ups.  
  7. * Potentially incompatible changes: disables the harmful 'make install'  
  8.   command in autotools.  

就是最下面一行,make install禁用了,鬱悶了吧?UNIX的習慣編譯方法:./configure;make;make install失靈了,只能說google比較有種,又開始挑戰使用者習慣了。

那麼怎麼編譯呢?

先進入gtest目錄(解壓gtest.zip包過程就不說了),執行以下兩行命令:

[cpp] view plain copy
  1. g++ -I./include -I./ -c ./src/gtest-all.cc  
  2. ar -rv libgtest.a gtest-all.o  

之後,生成了libgtest.a,這個就是我們要的東東了。以後寫自己的單元測試,就需要libgtest.a和gtest目錄下的include目錄,所以,這1檔案1目錄我們需要拷貝到自己的工程中。

編譯完成後怎麼驗證是否成功了呢?(相當不友好!)

[cpp] view plain copy
  1. cd ${GTEST_DIR}/make  
  2.   make  
  3.   ./sample1_unittest  

如果看到:

[cpp] view plain copy
  1. Running main() from gtest_main.cc  
  2. [==========] Running 6 tests from 2 test cases.  
  3. [----------] Global test environment set-up.  
  4. [----------] 3 tests from FactorialTest  
  5. [ RUN      ] FactorialTest.Negative  
  6. [       OK ] FactorialTest.Negative (0 ms)  
  7. [ RUN      ] FactorialTest.Zero  
  8. [       OK ] FactorialTest.Zero (0 ms)  
  9. [ RUN      ] FactorialTest.Positive  
  10. [       OK ] FactorialTest.Positive (0 ms)  
  11. [----------] 3 tests from FactorialTest (0 ms total)  
  12.   
  13. [----------] 3 tests from IsPrimeTest  
  14. [ RUN      ] IsPrimeTest.Negative  
  15. [       OK ] IsPrimeTest.Negative (0 ms)  
  16. [ RUN      ] IsPrimeTest.Trivial  
  17. [       OK ] IsPrimeTest.Trivial (0 ms)  
  18. [ RUN      ] IsPrimeTest.Positive  
  19. [       OK ] IsPrimeTest.Positive (0 ms)  
  20. [----------] 3 tests from IsPrimeTest (0 ms total)  
  21.   
  22. [----------] Global test environment tear-down  
  23. [==========] 6 tests from 2 test cases ran. (0 ms total)  
  24. [  PASSED  ] 6 tests.  

那麼證明編譯成功了。


2、如何編寫單元測試用例

以一個例子來說。我寫了一個開地址的雜湊表,它有del/get/add三個主要方法需要測試。在測試的時候,很自然,我只希望構造一個雜湊表物件,對之做許多種不同組合的操作,以驗證三個方法是否正常。所以,gtest提供的TEST方式我不會用,因為多個TEST不能共享同一份資料,而且還有初始化雜湊表物件的過程呢。所以我用TEST_F方式。TEST_F是一個巨集,TEST_F(classname, casename){}在函式體內去做具體的驗證。



上面是我要執行單元測試的類圖。那麼,我需要寫一系列單元測試用例來測試這個類。用gtest,首先要宣告一個類,繼承自gtest裡的Test類:



程式碼很簡單:

[cpp] view plain copy
  1. class CHashTableTest : public ::testing::Test {  
  2. protected:  
  3.     CHashTableTest():ht(100){  
  4.   
  5.     }  
  6.     virtual void SetUp() {  
  7.         key1 = "testkey1";  
  8.         key2 = "testkey2";  
  9.     }  
  10.   
  11.     // virtual void TearDown() {}  
  12.     CHashTable ht;  
  13.   
  14.     string key1;  
  15.     string key2;  
  16. };  


然後開始寫測試用例,用例裡可以直接使用上面類中的成員。



[cpp] view plain copy
  1. TEST_F(CHashTableTest, hashfunc)  
  2. {  
  3.     CHashElement he;  
  4.   
  5.     ASSERT_NE(\  
  6.             ht.getHashKey((char*)key1.c_str(), key1.size(), 0),\  
  7.             ht.getHashKey((char*)key2.c_str(), key2.size(), 0));  
  8.   
  9.     ASSERT_NE(\  
  10.             ht.getHashKey((char*)key1.c_str(), key1.size(), 0),\  
  11.             ht.getHashKey((char*)key1.c_str(), key1.size(), 1));  
  12.   
  13.     ASSERT_EQ(\  
  14.             ht.getHashKey((char*)key1.c_str(), key1.size(), 0),\  
  15.             ht.getHashKey((char*)key1.c_str(), key1.size(), 0));  
  16. }  

注意,TEST_F巨集會直接生成一個類,這個類繼承自上面我們寫的CHashTableTest類。

gtest提供ASSERT_和EXPECT_系列的巨集,用於判斷二進位制、字串等物件是否相等、真假等等。這兩種巨集的區別是,ASSERT_失敗了不會往下執行,而EXPECT_會繼續。


3、如何執行單元測試

首先,我們自己要有一個main函式,函式內容非常簡單:

[cpp] view plain copy
  1. #include "gtest/gtest.h"  
  2.   
  3. int main(int argc, char** argv) {  
  4.     testing::InitGoogleTest(&argc, argv);  
  5.   
  6.     // Runs all tests using Google Test.  
  7.     return RUN_ALL_TESTS();  
  8. }  

InitGoogleTest會解析引數。RUN_ALL_TESTS會把整個工程裡的TEST和TEST_F這些函式全部作為測試用例執行一遍。


執行時,假設我們編譯出的可執行檔案叫unittest,那麼直接執行./unittest就會輸出結果到螢幕,例如:

[cpp] view plain copy
  1. [==========] Running 4 tests from 1 test case.  
  2. [----------] Global test environment set-up.  
  3. [----------] 4 tests from CHashTableTest  
  4. [ RUN      ] CHashTableTest.hashfunc  
  5. [       OK ] CHashTableTest.hashfunc (0 ms)  
  6. [ RUN      ] CHashTableTest.addget  
  7. [       OK ] CHashTableTest.addget (0 ms)  
  8. [ RUN      ] CHashTableTest.add2get  
  9. testCHashTable.cpp:79: Failure  
  10. Value of: getHe->m_pNext==NULL  
  11.   Actual: true  
  12. Expected: false  
  13. [  FAILED  ] CHashTableTest.add2get (1 ms)  
  14. [ RUN      ] CHashTableTest.delget  
  15. [       OK ] CHashTableTest.delget (0 ms)  
  16. [----------] 4 tests from CHashTableTest (1 ms total)  
  17.   
  18. [----------] Global test environment tear-down  
  19. [==========] 4 tests from 1 test case ran. (1 ms total)  
  20. [  PASSED  ] 3 tests.  
  21. [  FAILED  ] 1 test, listed below:  
  22. [  FAILED  ] CHashTableTest.add2get  

[cpp] view plain copy
  1.   

可以看到,對於錯誤的CASE,會標出所在檔案及其行數。

如果我們需要輸出到XML檔案,則執行./unittest --gtest_output=xml,那麼會在當前目錄下生成test_detail.xml 檔案,內容如下:

[cpp] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <testsuites tests="3" failures="0" disabled="0" errors="0" time="0.001" name="AllTests">  
  3.   <testsuite name="CHashTableTest" tests="3" failures="0" disabled="0" errors="0" time="0.001">  
  4.     <testcase name="hashfunc" status="run" time="0.001" classname="CHashTableTest" />  
  5.     <testcase name="addget" status="run" time="0" classname="CHashTableTest" />  
  6.     <testcase name="delget" status="run" time="0" classname="CHashTableTest" />  
  7.   </testsuite>  
  8. </testsuites>  

如此,一個簡單的單元測試寫完。因為太簡單,所以不需要使用google mock模擬一些依賴。後續我再寫結合google mock來寫一些複雜的gtest單元測試。

下面來簡單說下gtest的工作流程。


4、google test內部是如何執行我們的單元測試用例的

首先從main函式看起。

我們的main函式執行了RUN_ALL_TESTS巨集,這個巨集幹了些什麼事呢?

[cpp] view plain copy
  1. #define RUN_ALL_TESTS()\  
  2.   (::testing::UnitTest::GetInstance()->Run())  
  3.   
  4. }  // namespace testing  

原來是呼叫了UnitTest靜態工廠例項的Run方法!在gtest裡,一切測試用例都是Test類的例項!所以,Run方法將會執行所有的Test例項來執行所有的單元測試,看看類圖:


為什麼說一切單元測試用例都是Test類的例項呢?

我們有兩種寫測試用例的方法,一種就是上面我說的TEST_F巨集,這要求我們要顯示的定義一個子類繼承自Test類。在TEST_F巨集裡,會再次定義一個新類,繼承自我們上面定義的子類(兩重繼承哈)。

第二種就是TEST巨集,這個巨集裡不要求使用者程式碼定義類,但在google test裡,TEST巨集還是定義了一個子類繼承自Test類。

所以,UnitTest的Run方法只需要執行所有Test例項即可。


每個單元測試用例就是一個Test類子類的例項。它同時與TestResult,TestCase,TestInfo關聯起來,用於提供結果。

當然,還有EventListen類來監控結果的輸出,控制測試的進度等。


以上並沒有深入細節,只是大致幫助大家理解,我們寫的幾個簡單的gtest巨集,和單元測試用例,到底是如何被執行的。接下來,我會通過gmock來深入的看看google單元測試的玩法。

相關文章