做爲一個TDD的腦殘粉,凡事都想測試先行。如今迴歸C++開發,頓時以爲各類不方便。特別是手頭沒有好用的單元測試框架,開發效率簡直沒法忍受,寫好的代碼重構起來也畏手畏腳。框架
曾經嘗試過CppUnit,做爲JUnit的同胞,出自名門,但在C++開發中,尤爲在VS環境下實在是太難用了(主要是因爲 C++ 沒有反射機制,而這是 JUnit 設計的基礎)。偶然搜得googletest
,一套由 google 發佈的開源單元測試框架。
應用 googletest
編寫單元測試代碼函數
googletest
是由 Google 公司發佈,且遵循 New BSD License (可用做商業用途)的開源項目,而且 googletest
能夠支持絕大多數你們所熟知的平臺。與 CppUnit 不一樣的是: googletest
能夠自動記錄下全部定義好的測試,不須要用戶經過列舉來指明哪些測試須要運行。單元測試
在應用 googletest
編寫單元測試時,使用 TEST()
宏來聲明測試函數。如:測試
清單 1. 用 TEST()
宏聲明測試函數google
TEST(GlobalConfigurationTest, configurationDataTest) TEST(GlobalConfigurationTest, noConfigureFileTest)
分別針對同一程序單元 GlobalConfiguration 聲明瞭兩個不一樣的測試函數,以分別對配置數據進行檢查configurationDataTest,以及測試沒有配置文件的特殊狀況noConfigureFileTest。設計
針對同一程序單元設計出不一樣的測試場景後(即劃分出不一樣的 Test 後),開發者就能夠編寫單元測試分別實現這些測試場景了。指針
在 googletest
中實現單元測試,可經過 ASSERT_*
和 EXPECT_*
斷言來對程序運行結果進行檢查。 ASSERT_*
版本的斷言失敗時會產生致命失敗,並結束當前函數; EXPECT_*
版本的斷言失敗時產生非致命失敗,但不會停止當前函數。所以, ASSERT_*
經常被用於後續測試邏輯強制依賴的處理結果的斷言,如建立對象後檢查指針是否爲空,若爲空,則後續對象方法調用會失敗;而 EXPECT_*
則用於即便失敗也不會影響後續測試邏輯的處理結果的斷言,如某個方法返回結果的多個屬性的檢查。code
googletest
中定義了以下的斷言:
表 1: googletest
定義的斷言( Assert )orm
基本斷言 | 二進制比較 | 字符串比較 |
---|---|---|
ASSERT_TRUE(condition); condition爲真 | ASSERT_EQ(expected,actual); expected==actual | ASSERT_STREQ(expected_str,actual_str); 兩個 C 字符串有相同的內容 |
EXPECT_TRUE(condition); condition爲真 | EXPECT_EQ(expected,actual); expected==actual | EXPECT_STREQ(expected_str,actual_str); 兩個 C 字符串有相同的內容 |
ASSERT_FALSE(condition); condition爲假 | ASSERT_NE(val1,val2); val1!=val2 | ASSERT_STRNE(str1,str2); 兩個 C 字符串有不一樣的內容 |
EXPECT_FALSE(condition); condition爲假 | EXPECT_NE(val1,val2); val1!=val2 | EXPECT_STRNE(str1,str2); 兩個 C 字符串有不一樣的內容 |
ASSERT_LT(val1,val2); val1
|
ASSERT_STRCASEEQ(expected_str,actual_str); 兩個 C 字符串有相同的內容,忽略大小寫 | |
EXPECT_LT(val1,val2); val1
|
EXPECT_STRCASEEQ(expected_str,actual_str); 兩個 C 字符串有相同的內容,忽略大小寫 | |
ASSERT_LE(val1,val2); val1 | ASSERT_STRCASENE(expected_str,actual_str); 兩個 C 字符串有不一樣的內容,忽略大小寫 | |
EXPECT_LE(val1,val2); val1 | EXPECT_STRCASENE(expected_str,actual_str); 兩個 C 字符串有不一樣的內容,忽略大小寫 | |
ASSERT_GT(val1,val2); val1>val2 | ||
EXPECT_GT(val1,val2); val1>val2 | ||
ASSERT_GE(val1,val2); val1>=val2 | ||
EXPECT_GE(val1,val2); val1>=val2 |
下面的實例演示了上面部分斷言的使用:
清單 2. 一個較完整的 googletest
單元測試實例對象
// Configure.h #pragma once #include <string> #include <vector> class Configure { private: std::vector<std::string> vItems; public: int addItem(std::string str); std::string getItem(int index); int getSize(); }; // Configure.cpp #include "Configure.h" #include <algorithm> /** * @brief Add an item to configuration store. Duplicate item will be ignored * @param str item to be stored * @return the index of added configuration item */ int Configure::addItem(std::string str) { std::vector<std::string>::const_iterator vi=std::find(vItems.begin(), vItems.end(), str); if (vi != vItems.end()) return vi - vItems.begin(); vItems.push_back(str); return vItems.size() - 1; } /** * @brief Return the configure item at specified index. * If the index is out of range, "" will be returned * @param index the index of item * @return the item at specified index */ std::string Configure::getItem(int index) { if (index >= vItems.size()) return ""; else return vItems.at(index); } /// Retrieve the information about how many configuration items we have had int Configure::getSize() { return vItems.size(); } // ConfigureTest.cpp #include <gtest/gtest.h> #include "Configure.h" TEST(ConfigureTest, addItem) { // do some initialization Configure* pc = new Configure(); // validate the pointer is not null ASSERT_TRUE(pc != NULL); // call the method we want to test pc->addItem("A"); pc->addItem("B"); pc->addItem("A"); // validate the result after operation EXPECT_EQ(pc->getSize(), 2); EXPECT_STREQ(pc->getItem(0).c_str(), "A"); EXPECT_STREQ(pc->getItem(1).c_str(), "B"); EXPECT_STREQ(pc->getItem(10).c_str(), ""); delete pc; }
運行單元測試
在實現完單元測試的測試邏輯後,能夠經過 RUN_ALL_TESTS()
來運行它們,若是全部測試成功,該函數返回 0,不然會返回 1 。 RUN_ALL_TESTS()
會運行你連接到的全部測試――它們能夠來自不一樣的測試案例,甚至是來自不一樣的文件。
所以,運行 googletest
編寫的單元測試的一種比較簡單可行的方法是:
爲每個被測試的 class
分別建立一個測試文件,並在該文件中編寫針對這一 class
的單元測試;
編寫一個 Main.cpp
文件,並在其中包含如下代碼,以運行全部單元測試:
清單 3. 初始化 googletest
並運行全部測試
#include <gtest/gtest.h> int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); // Runs all tests using Google Test. return RUN_ALL_TESTS(); }
最後,將全部測試代碼及 Main.cpp
編譯並連接到目標程序中。
此外,在運行可執行目標程序時,可使用 --gtest_filter
來指定要執行的測試用例,如:
./foo_test
沒有指定 filter ,運行全部測試;./foo_test --gtest_filter=*
指定 filter 爲 *
,運行全部測試;./foo_test --gtest_filter=FooTest.*
運行測試用例 FooTest
的全部測試;./foo_test --gtest_filter=*Null*:*Constructor*
運行全部全名(即測試用例名 + 「 . 」 + 測試名,如 GlobalConfigurationTest.noConfigureFileTest
)含有」Null
」 或」Constructor
」 的測試;./foo_test --gtest_filter=FooTest.*-FooTest.Bar
運行測試用例 FooTest
的全部測試,但不包括 FooTest.Bar
。 這一特性在包含大量測試用例的項目中會十分有用。