C++ 單元測試框架 Google Test (gtest)

一、相關概念

gtest是google公司發佈的一個跨平臺(Liunx、Mac OS、Windows 等) 的C++單元測試框架,它提供了豐富的斷言、致命和非致命判斷、參數化、”死亡測試”等等。

Test 使用斷言來判斷測試代碼的行爲:如果一個 Test 崩潰了或者出現了一個失敗的斷言,則該 Test 就失敗了;反之,它就是成功的。

Test case (有的也叫Test suit) 包括一個或多個 Test。我們應當把 Test 打包、分組,放入 Test Case 中,以便測試代碼的結構更加清晰。當一個 Test Case 中的多個 Test 需要共享對象和子程序時,我們可以把這些共享內容放入一個測試夾具(test fixture)類中。一個測試程序可以包含多個 Test Case。

兩種斷言:

  • ASSERT_* :當斷言失敗時,產生致命錯誤,並終止當前函數;
  • EXPECT_* :當斷言失敗時,產生非致命錯誤,並且不會終止當前函數。

通常,我們都會選擇 EXPECT_*,因爲它能讓我們在一次測試中測試出更多的失敗情況。不過,如果我們想要在出現失敗的測試時立即終止程序,則要選擇 ASSERT_*。

注意:因爲 ASSERT_* 會在失敗時立即終止函數,那麼就可能跳過後面程序中進行清理工作的代碼,由此可能會產生內存泄露。所以我們在使用 ASSERT_* 時,要留心檢查堆內存,防止內存泄露。

基本斷言

Fatal assertion Nonfatal assertion Verifies
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

字符串比較

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 contents
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 contents, ignoring case

二、安裝

git clone https://github.com/google/googletest
cd googletest
mkdir build
cd build
cmake .. -DCMAKE_CXX_FLAGS='-std=c++11' # 不指定c++11標準會報錯
make
sudo make install

安裝完之後生成的頭文件位於/usr/local/include/gtest/下,靜態庫文件(libgtest.a, libgtest_main.a)位於/usr/local/lib/下。

三、使用

(1) TEST() 宏

TEST() 宏的第一個參數是 Test Case 的名稱,第二個參數是(隸屬於第一個Test Case參數的)Test 的名稱。一個測試的完整名稱包括 Test Case 名稱及 Test 的名稱,不同 Test Case 的 Test 名稱可以相同。googletest 根據 test case 對測試結果進行分組,所以一些相關的 test 應當放入同一個 test case 中。

main.cc:

#include "gtest/gtest.h"
 
bool IsPositive(int num) {
    return num > 0? true : false;
}
 
TEST(PositiveTest, HandlesPositiveInput) {
    EXPECT_TRUE(IsPositive(1));
}

TEST(PositiveTest, HandlesNegativeInput) {
    EXPECT_FALSE(IsPositive(-1));
}

int main(int argc, char **argv) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

上述代碼中編寫了兩個 test,分別爲:HandlesPositiveInput 和 HandlesNegativeInput,這兩個 test 都屬於同一個 test case(PositiveTest),編譯並執行上述代碼,結果如下:

(2) TEST_F() 宏

當我們想讓多個 test 使用同一套數據配置時,就需要用到 Test Fixtures 了。創建 test fixtures 的具體方法如下:

  1. 派生一個繼承 ::testing::Test 的類,並將該類中的一些內容聲明爲 protected 類型,以便在子類中進行訪問;
  2. 根據實際情況,編寫默認的構造函數或SetUp()函數,來爲每個 test 準備所需內容;
  3. 根據實際情況,編寫默認的析構函數或TearDown()函數,來釋放SetUp()中分配的資源;
  4. 根據實際情況,定義 test 共享的子程序。

當使用 fixture 時,我們使用 TEST_F() 宏代替 TEST() 宏,TEST_F() 允許我們在 test fixture 中訪問對象和子程序。

注意:TEST_F() 的第一個參數(即 test case 的名稱)必須是 test fixture 類的名字。

對於 TEST_F() 定義的每個 test,googletest 將會在運行時創建一個新的 test fixture,並立即通過 SetUp() 對其進行初始化,然後運行 test,之後通過調用 TearDown() 進行數據清理,最後刪除 test fixture。需要注意的是,同一個 test case 中不同的 test 具有不同的 test fixture 對象,並且 googletest 每次創建新的 test fixture 前都會先刪除之前的 test fixture。多個 test 不會重用相同的 test fixture,某個 test 對 fixture 進行的修改對其他 test 無影響。

main.cc:

#include "gtest/gtest.h"

// 需要測試的類(減法的實現有錯)
class Foo {
public:
    Foo(int num) {
        num_ = num;
    };
    void Add(int n) {
        num_ += n;
    };
    void Minus(int n) {
        num_ = n;
    };
    int GetNum() {
        return num_;
    };
private:
    int num_;
};

// 定義測試夾具類FooTest
class FooTest: public testing::Test {
protected:
    Foo* foo;
    // Code here will be called immediately after the constructor (right before each test)
    void SetUp() {
        foo = new Foo(1);
    }
    // Code here will be called immediately after each test (right before the destructor)
    void TearDown() {
        delete foo;
    }
};

TEST_F(FooTest, test_add) {
    EXPECT_EQ(foo->GetNum(), 1);
    foo->Add(1);
    EXPECT_EQ(foo->GetNum(), 2);
}

TEST_F(FooTest, test_minus) {
    EXPECT_EQ(foo->GetNum(), 1);
    foo->Minus(1);
    EXPECT_EQ(foo->GetNum(), 0);
}
 
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

上述代碼中編寫了兩個 test,分別爲:test_add、和 test_minus,這兩個 test 都屬於同一個 test case(即 FooTest)。

上述代碼中的 test 運行時,主要會進行如下操作:

googletest 構造一個 FooTest 類的對象(我們稱之爲 f1);
f1.SetUp() 函數對 f1 進行初始化;
使用對象 f1 運行第一個 test(test_add);
f1.TearDown() 在 test 完成後,進行清理工作;
對象 f1 被析構。
上述5個步驟在另一個 FooTest 類的對象(如 f2)中重複,此次會運行 test_minus。
編譯並執行上述代碼,結果如下:

(3) 全局事件

要實現全局事件,必須寫一個類,繼承testing::Environment類,實現裏面的SetUp和TearDown方法。SetUp方法在所有案例執行前執行;TearDown方法在所有案例執行後執行。除了要繼承testing::Environment類,還要定義一個該全局環境的一個對象並將該對象添加到全局環境測試中去。

main.cc 

#include "gtest/gtest.h"

class GlobalTest: public testing::Environment {
public:
    void SetUp() {
        std::cout << "SetUp" << std::endl;
    }
    void TearDown() {
        std::cout << "TearDown" << std::endl;
    }
};

TEST(abs_test, test_1) {
    EXPECT_EQ(std::abs(-1), 1);
}

TEST(abs_test, test_2) {;
    EXPECT_EQ(std::abs(0), 0);
}
 
int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    testing::Environment* env = new GlobalTest();
    testing::AddGlobalTestEnvironment(env);
    return RUN_ALL_TESTS();
}

編譯並執行上述代碼,結果如下: 

(4) 死亡測試

這裏的”死亡”指的是程序的崩潰。通常在測試的過程中,我們需要考慮各種各樣的輸入,有的輸入可能直接導致程序崩潰,這個時候我們就要檢查程序是否按照預期的方式掛掉,這也就是所謂的”死亡測試”。

死亡測試所用到的宏:

ASSERT_DEATH(參數1,參數2),程序掛了並且錯誤信息和參數2匹配,此時認爲測試通過。如果參數2爲空字符串,則只需要看程序掛沒掛即可。
ASSERT_EXIT(參數1,參數2,參數3),語句停止並且錯誤信息和被提前給的信息匹配。

main.cc

#include "gtest/gtest.h"

int func() {
    int *ptr = NULL;
    *ptr = 100;
    return 0;
}

TEST(FuncDeathTest, Nullptr) {
    ASSERT_DEATH(func(), "");
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

編譯並執行上述代碼,結果如下: 

 

main 函數說明

::testing::InitGoogleTest() 函數將會解析命令行中的 googletest 參數,它允許用戶通過多樣的命令行參數來控制測試程序的行爲(即定製命令行參數的功能)。

::testing::InitGoogleTest() 函數必須要在 RUN_ALL_TESTS() 之前調用,否則對應的 flag 可能不會被正常地初始化。

RUN_ALL_TESTS() 會運行所有關聯的 test,這些 test 可以來自不同的 test case,甚至不同的源文件。

RUN_ALL_TESTS() 宏在所有 test 都成功時,返回0;否則返回1。

main() 函數必須要返回 RUN_ALL_TESTS() 宏的結果。

RUN_ALL_TESTS() 只能運行一次,多次調用會與 googletest 的一些功能(如 thread-safe death tests)發生衝突。 

編譯 

Google Test 是線程安全的,其線程安全特性要依賴 pthreads 庫。 

(1) g++編譯

g++ -std=c++11 main.cc /usr/local/lib/libgtest.a -lpthread -o ./bin/test

(2) cmake編譯

CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)
project(hello_world)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIR})

add_executable(hello_world main.cc)
target_link_libraries(hello_world ${GTEST_LIBRARY} pthread)

參考

https://github.com/google/googletest/blob/master/googletest/docs/primer.md

https://blog.csdn.net/liitdar/article/details/85712973 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章