googletest

做爲一個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。 這一特性在包含大量測試用例的項目中會十分有用。

相關文章
相關標籤/搜索