gTest詳解

sld666666發表於2015-05-19

1. 安裝使用

1.1 安裝

在https://code.google.com/p/googletest/ 下載原始碼
進入msvc, 注意編譯方式, 如果是dll, 選擇 gtest-md
編譯生成lib檔案, 然後引入.檔案即可使用

1.2 使用

#include "gtest/gtest.h"
int _tmain(int argc, _TCHAR* argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

當然我們也可以輸出到xml

int _tmain(int argc, _TCHAR* argv[])
{
    testing::GTEST_FLAG(output) = "xml:";
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

2. 斷言

斷言的巨集可以分為兩類ASSERT系列和EXPECT系列。

TEST(StringCmpTest, Demo)
{
    EXPECT_EQ(3, add(1, 2));
    ASSERT_EQ(3, add(1, 2));
}

我們再來看下所支援的巨集

  • ASSERT_EQ
  • ASSERT_NE
  • ASSERT_LE
  • ASSERT_LT
  • ASSERT_GE
  • ASSERT_GT

  • EXPECT_EQ
  • EXPECT_NE
  • EXPECT_LE
  • EXPECT_LT
  • EXPECT_GE
  • EXPECT_GT

  • ASSERT_TRUE
  • ASSERT_FALSE

  • ASSERT_STREQ
  • ASSERT_STRNE
  • ASSERT_STRCASEEQ
  • ASSERT_STRCASENE

  • ASSERT_FLOAT_EQ
  • ASSERT_DOUBLE_EQ

直接返回成功還是失敗

  • FAIL();
  • ADD_FAILURE();

Predicate Assertions

在使用EXPECT_TRUE或ASSERT_TRUE時,有時希望能夠輸出更加詳細的資訊,比如檢查一個函式的返回值TRUE還是FALSE時,希望能夠輸出傳入的引數是什麼,以便失敗後好跟蹤。因此提供瞭如下的斷言:

  • ASSERT_PRED1(pred1, val1);
  • ASSERT_PRED2(pred2, val1, val2);

如果對這樣的輸出不滿意的話,還可以自定義輸出格式化

  • ASSERT_PRED_FORMAT1(pred_format1, val1);`
  • ASSERT_PRED_FORMAT2(pred_format2, val1, val2);

例子

如果我們有這樣一個類Arithmetic
我們只需要新建一個ArithmeticUnit.cpp檔案,然後寫下如下程式碼:

#include "stdafx.h"
#include "Arithmetic.h"
#include "gtest/gtest.h"

TEST(Arithmetic, add){
    Arithmetic arith;
    int a(1), b(2);
    EXPECT_EQ(3, arith.add(1, 2));
}

3. 深入解析gTest

首先從TEST巨集入手, 我們看下巨集的定義

//1
 define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)

//2
#define GTEST_TEST(test_case_name, test_name)\
GTEST_TEST_(test_case_name, test_name, \
          ::testing::Test, ::testing::internal::GetTestTypeId())

//3
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
public:\
    GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
private:\
    virtual void TestBody();\
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
    GTEST_DISALLOW_COPY_AND_ASSIGN_(\
    GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
....

所以
1. 最終展開的巨集是繼承自testing::Test類
2. 我們最終寫的程式碼是放在TestBody()中的
3. 通過靜態變數test_info_,呼叫MakeAndRegisterTestInfo對測試案例進行註冊。

看下MakeAndRegisterTestInfo 是如何實現的

TestInfo* MakeAndRegisterTestInfo(
    const char* test_case_name,
    const char* name,
    const char* type_param,
    const char* value_param,
    TypeId fixture_class_id,
    SetUpTestCaseFunc set_up_tc,
    TearDownTestCaseFunc tear_down_tc,
    TestFactoryBase* factory) {
  TestInfo* const test_info =
      new TestInfo(test_case_name, name, type_param, value_param,
                   fixture_class_id, factory);
  GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
  return test_info;
}

TestInfo物件主要用於包含如下資訊:

  1. 測試案例名稱(testcase name)
  2. 測試名稱(test name)

  3. 該案例是否需要執行

  4. 執行案例時,用於建立Test物件的函式指標
  5. 測試結果

我們還看到,TestInfo的建構函式中,非常重要的一個引數就是工廠物件
internal::TestFactoryBase* factory
它主要負責在執行測試案例時建立出Test物件

new ::testing::internal::TestFactoryImpl<\
        GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>)

我們再來看下 TestFactoryImpl 是如何實現的

template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
 public:
  virtual Test* CreateTest() { return new TestClass; }
};

我靠, 這也能算是工廠嗎~

不過總之流程是, 我們要建立一個測試物件的時候,先呼叫factory的CreateTest()方法 建立TestInfo物件, 再通過 GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);對TestInfo物件進行註冊

UnitTest 是單例
UnitTestImpl 是實現

void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc,
               Test::TearDownTestCaseFunc tear_down_tc,
               TestInfo * test_info) {
// 獲取或建立了一個TestCase物件,並將testinfo新增到TestCase物件中。
GetTestCase(test_info->test_case_name(),
            test_info->test_case_comment(),
            set_up_tc,
            tear_down_tc)->AddTestInfo(test_info);
}

這裡TestCase物件就出來了

  1. TEST巨集中的兩個引數,第一個引數testcase_name,就是TestCase物件的名稱,第二個引數test_name就是Test物件的名稱。而TestInfo包含了一個測試案例的一系列資訊。

  2. 一個TestCase物件對應一個或多個TestInfo物件。

總結一下gtest裡的幾個關鍵的物件:

  1. UnitTest 單例,總管整個測試,包括測試環境資訊,當前執行狀態等等
  2. Test 我們自己編寫的,或通過TEST,TEST_F等巨集展開後的Test物件,管理著測試案例的前後事件,具體的執行程式碼TestBody。
  3. TestCase 測試案例物件,管理著基於TestCase的前後事件,管理內部多個TestInfo。
  4. TestInfo 管理著測試案例的基本資訊,包括Test物件的建立方法。

一個簡單的UML圖如下
gTest詳解

4. 擼一個山寨的

擼了一個山寨的,https://github.com/sld666666/TestUnit