一、前言
“深入解析”對(duì)我來(lái)說(shuō)的確有些難度,所以我盡量將我學(xué)習(xí)到和觀察到的gtest內(nèi)部實(shí)現(xiàn)介紹給大家。本文算是拋磚引玉吧,只能是對(duì)gtest的整體結(jié)構(gòu)的一些介紹,想要了解更多細(xì)節(jié)最好的辦法還是看gtest源碼,如果你看過(guò)gtest源碼,你會(huì)發(fā)現(xiàn)里面的注釋非常的詳細(xì)!好了,下面就開(kāi)始了解gtest吧。
二、從TEST宏開(kāi)始
前面的文章已經(jīng)介紹過(guò)TEST宏的用法了,通過(guò)TEST宏,我們可以非法簡(jiǎn)單、方便的編寫(xiě)測(cè)試案例,比如:
TEST(FooTest, Demo){ EXPECT_EQ(1, 1);}
我們先不去看TEST宏的定義,而是先使用/P參數(shù)將TEST展開(kāi)。如果使用的是Vistual Studio的話:
1. 選中需要展開(kāi)的代碼文件,右鍵 - 屬性 - C/C++ - PReprocessor
2. Generate Preprocessed File 設(shè)置 Without Line Numbers (/EP /P) 或 With Line Numbers (/P)
3. 關(guān)閉屬性對(duì)話框,右鍵選中需要展開(kāi)的文件,右鍵菜單中點(diǎn)擊:Compile
編譯過(guò)后,會(huì)在源代碼目錄生成一個(gè)后綴為.i的文件,比如我對(duì)上面的代碼進(jìn)行展開(kāi),展開(kāi)后的內(nèi)容為:
class FooTest_Demo_Test : public ::testing::Test {public: FooTest_Demo_Test() {}private: virtual void TestBody(); static ::testing::TestInfo* const test_info_; FooTest_Demo_Test(const FooTest_Demo_Test &); void Operator=(const FooTest_Demo_Test &);};::testing::TestInfo* const FooTest_Demo_Test ::test_info_ = ::testing::internal::MakeAndRegisterTestInfo( "FooTest", "Demo", "", "", (::testing::internal::GetTestTypeId()), ::testing::Test::SetUpTestCase, ::testing::Test::TearDownTestCase, new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>);void FooTest_Demo_Test::TestBody(){ switch (0) case 0: if (const ::testing::AssertionResult gtest_ar = (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "1", 1, 1))) ; else ::testing::internal::AssertHelper( ::testing::TPRT_NONFATAL_FAILURE, ".//gtest_demo.cpp", 9, gtest_ar.failure_message() ) = ::testing::Message();}
展開(kāi)后,我們觀察到:
1. TEST宏展開(kāi)后,是一個(gè)繼承自testing::Test的類(lèi)。
2. 我們?cè)赥EST宏里面寫(xiě)的測(cè)試代碼,其實(shí)是被放到了類(lèi)的TestBody方法中。
3. 通過(guò)靜態(tài)變量test_info_,調(diào)用MakeAndRegisterTestInfo對(duì)測(cè)試案例進(jìn)行注冊(cè)。
如下圖:

上面關(guān)鍵的方法就是MakeAndRegisterTestInfo了,我們跳到MakeAndRegisterTestInfo函數(shù)中:
// 創(chuàng)建一個(gè) TestInfo 對(duì)象并注冊(cè)到 Google Test;// 返回創(chuàng)建的TestInfo對(duì)象//// 參數(shù)://// test_case_name: 測(cè)試案例的名稱(chēng)// name: 測(cè)試的名稱(chēng)// test_case_comment: 測(cè)試案例的注釋信息// comment: 測(cè)試的注釋信息// fixture_class_id: test fixture類(lèi)的ID// set_up_tc: 事件函數(shù)SetUpTestCases的函數(shù)地址// tear_down_tc: 事件函數(shù)TearDownTestCases的函數(shù)地址// factory: 工廠對(duì)象,用于創(chuàng)建測(cè)試對(duì)象(Test)TestInfo* MakeAndRegisterTestInfo( const char* test_case_name, const char* name, const char* test_case_comment, const char* comment, TypeId fixture_class_id, SetUpTestCaseFunc set_up_tc, TearDownTestCaseFunc tear_down_tc, TestFactoryBase* factory) { TestInfo* const test_info = new TestInfo(test_case_name, name, test_case_comment, comment, fixture_class_id, factory); GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); return test_info;}
我們看到,上面創(chuàng)建了一個(gè)TestInfo對(duì)象,然后通過(guò)AddTestInfo注冊(cè)了這個(gè)對(duì)象。TestInfo對(duì)象到底是一個(gè)什么樣的東西呢?
TestInfo對(duì)象主要用于包含如下信息:
1. 測(cè)試案例名稱(chēng)(testcase name)
2. 測(cè)試名稱(chēng)(test name)
3. 該案例是否需要執(zhí)行
4. 執(zhí)行案例時(shí),用于創(chuàng)建Test對(duì)象的函數(shù)指針
5. 測(cè)試結(jié)果
我們還看到,TestInfo的構(gòu)造函數(shù)中,非常重要的一個(gè)參數(shù)就是工廠對(duì)象,它主要負(fù)責(zé)在運(yùn)行測(cè)試案例時(shí)創(chuàng)建出Test對(duì)象。我們看到我們上面的例子的factory為:
new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>
我們明白了,Test對(duì)象原來(lái)就是TEST宏展開(kāi)后的那個(gè)類(lèi)的對(duì)象(FooTest_Demo_Test),再看看TestFactoryImpl的實(shí)現(xiàn):
template <class TestClass>class TestFactoryImpl : public TestFactoryBase {public: virtual Test* CreateTest() { return new TestClass; }};
這個(gè)對(duì)象工廠夠簡(jiǎn)單吧,嗯,Simple is better。當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)測(cè)試對(duì)象(Test)時(shí),調(diào)用factory的CreateTest()方法就可以了。
創(chuàng)建了TestInfo對(duì)象后,再通過(guò)下面的方法對(duì)TestInfo對(duì)象進(jìn)行注冊(cè):
GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
GetUnitTestImpl()是獲取UnitTestImpl對(duì)象:
inline UnitTestImpl* GetUnitTestImpl() { return UnitTest::GetInstance()->impl();}
其中UnitTest是一個(gè)單件(Singleton),整個(gè)進(jìn)程空間只有一個(gè)實(shí)例,通過(guò)UnitTest::GetInstance()獲取單件的實(shí)例。上面的代碼看到,UnitTestImpl對(duì)象是最終是從UnitTest對(duì)象中獲取的。那么UnitTestImpl到底是一個(gè)什么樣的東西呢?可以這樣理解:
UnitTestImpl是一個(gè)在UnitTest內(nèi)部使用的,為執(zhí)行單元測(cè)試案例而提供了一系列實(shí)現(xiàn)的那么一個(gè)類(lèi)。(自己歸納的,可能不準(zhǔn)確)
我們上面的AddTestInfo就是其中的一個(gè)實(shí)現(xiàn),負(fù)責(zé)注冊(cè)TestInfo實(shí)例:
// 添加TestInfo對(duì)象到整個(gè)單元測(cè)試中//// 參數(shù)://// set_up_tc: 事件函數(shù)SetUpTestCases的函數(shù)地址// tear_down_tc: 事件函數(shù)TearDownTestCases的函數(shù)地址// test_info: TestInfo對(duì)象void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc, TestInfo * test_info) {// 處理死亡測(cè)試的代碼,先不關(guān)注它if (original_working_dir_.IsEmpty()) { original_working_dir_.Set(FilePath::GetCurrentDir()); if (original_working_dir_.IsEmpty()) { printf("%s/n", "Failed to get the current working directory."); abort(); }}// 獲取或創(chuàng)建了一個(gè)TestCase對(duì)象,并將testinfo添加到TestCase對(duì)象中。GetTestCase(test_info->test_case_name(), test_info->test_case_comment(), set_up_tc, tear_down_tc)->AddTestInfo(test_info);}
我們看到,TestCase對(duì)象出來(lái)了,并通過(guò)AddTestInfo添加了一個(gè)TestInfo對(duì)象。這時(shí),似乎豁然開(kāi)朗了:
1. TEST宏中的兩個(gè)參數(shù),第一個(gè)參數(shù)testcase_name,就是TestCase對(duì)象的名稱(chēng),第二個(gè)參數(shù)test_name就是Test對(duì)象的名稱(chēng)。而TestInfo包含了一個(gè)測(cè)試案例的一系列信息。
2. 一個(gè)TestCase對(duì)象對(duì)應(yīng)一個(gè)或多個(gè)TestInfo對(duì)象。

我們來(lái)看看TestCase的創(chuàng)建過(guò)程(UnitTestImpl::GetTestCase):
// 查找并返回一個(gè)指定名稱(chēng)的TestCase對(duì)象。如果對(duì)象不存在,則創(chuàng)建一個(gè)并返回//// 參數(shù)://// test_case_name: 測(cè)試案例名稱(chēng)// set_up_tc: 事件函數(shù)SetUpTestCases的函數(shù)地址// tear_down_tc: 事件函數(shù)TearDownTestCases的函數(shù)地址TestCase* UnitTestImpl::GetTestCase(const char* test_case_name, const char* comment, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc) { // 從test_cases里查找指定名稱(chēng)的TestCase internal::ListNode<TestCase*>* node = test_cases_.FindIf( TestCaseNameIs(test_case_name)); if (node == NULL) { // 沒(méi)找到,我們來(lái)創(chuàng)建一個(gè) TestCase* const test_case = new TestCase(test_case_name, comment, set_up_tc, tear_down_tc); // 判斷是否為死亡測(cè)試案例 if (internal::UnitTestOptions::MatchesFilter(String(test_case_name), kDeathTestCaseFilter)) { // 是的話,將該案例插入到最后一個(gè)死亡測(cè)試案例后 node = test_cases_.InsertAfter(last_death_test_case_, test_case); last_death_test_case_ = node; } else { // 否則,添加到test_cases最后。 test_cases_.PushBack(test_case); node = test_cases_.Last(); } } // 返回TestCase對(duì)象 return node->element();}
三、回過(guò)頭看看TEST宏的定義
#define TEST(test_case_name, test_name)/ GTEST_TEST_(test_case_name, test_name, / ::testing::Test, ::testing::internal::GetTestTypeId())
同時(shí)也看看TEST_F宏
#define TEST_F(test_fixture, test_name)/ GTEST_TEST_(test_fixture, test_name, test_fixture, / ::testing::internal::GetTypeId<test_fixture>())都是使用了GTEST_TEST_宏,在看看這個(gè)宏如何定義的:
#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_DISALLOW_COPY_AND_ASSIGN_(/ GTEST_TEST_CLASS_NAME_(test_case_name, test_name));/};//::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)/ ::test_info_ =/ ::testing::internal::MakeAndRegisterTestInfo(/ #test_case_name, #test_name, "", "", / (parent_id), / parent_class::SetUpTestCase, / parent_class::TearDownTestCase, / new ::testing::internal::TestFactoryImpl</ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);/void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
不需要多解釋了,和我們上面展開(kāi)看到的差不多,不過(guò)這里比較明確的看到了,我們?cè)赥EST宏里寫(xiě)的就是TestBody里的東西。這里再補(bǔ)充說(shuō)明一下里面的GTEST_DISALLOW_COPY_AND_ASSIGN_宏,我們上面的例子看出,這個(gè)宏展開(kāi)后:
FooTest_Demo_Test(const FooTest_Demo_Test &);void operator=(const FooTest_Demo_Test &);
正如這個(gè)宏的名字一樣,它是用于防止對(duì)對(duì)象進(jìn)行拷貝和賦值操作的。
四、再來(lái)了解RUN_ALL_TESTS宏
我們的測(cè)試案例的運(yùn)行就是通過(guò)這個(gè)宏發(fā)起的。RUN_ALL_TEST的定義非常簡(jiǎn)單:
#define RUN_ALL_TESTS()/ (::testing::UnitTest::GetInstance()->Run())
我們又看到了熟悉的::testing::UnitTest::GetInstance(),看來(lái)案例的執(zhí)行時(shí)從UnitTest的Run方法開(kāi)始的,我提取了一些Run中的關(guān)鍵代碼,如下:
int UnitTest::Run() { __try { return impl_->RunAllTests(); } __except(internal::UnitTestOptions::GTestShouldProcessSEH( GetExceptionCode())) { printf("Exception thrown with code 0x%x./nFAIL/n", GetExceptionCode()); fflush(stdout); return 1; } return impl_->RunAllTests();}
我們又看到了熟悉的impl(UnitTestImpl),具體案例該怎么執(zhí)行,還是得靠UnitTestImpl。
int UnitTestImpl::RunAllTests() { // ... printer->OnUnitTestStart(parent_); // 計(jì)時(shí) const TimeInMillis start = GetTimeInMillis(); printer->OnGlobalSetUpStart(parent_); // 執(zhí)行全局的SetUp事件 environments_.ForEach(SetUpEnvironment); printer->OnGlobalSetUpEnd(parent_); // 全局的SetUp事件執(zhí)行成功的話 if (!Test::HasFatalFailure()) { // 執(zhí)行每個(gè)測(cè)試案例 test_cases_.ForEach(TestCase::RunTestCase); } // 執(zhí)行全局的TearDown事件 printer->OnGlobalTearDownStart(parent_); environments_in_reverse_order_.ForEach(TearDownEnvironment); printer->OnGlobalTearDownEnd(parent_); elapsed_time_ = GetTimeInMillis() - start; // 執(zhí)行完成 printer->OnUnitTestEnd(parent_); // Gets the result and clears it. if (!Passed()) { failed = true; } ClearResult(); // 返回測(cè)試結(jié)果 return failed ? 1 : 0;}
上面,我們很開(kāi)心的看到了我們前面講到的全局事件的調(diào)用。environments_是一個(gè)Environment的鏈表結(jié)構(gòu)(List),它的內(nèi)容是我們?cè)趍ain中通過(guò):
testing::AddGlobalTestEnvironment(new FooEnvironment);
添加進(jìn)去的。test_cases_我們之前也了解過(guò)了,是一個(gè)TestCase的鏈表結(jié)構(gòu)(List)。gtest實(shí)現(xiàn)了一個(gè)鏈表,并且提供了一個(gè)Foreach方法,迭代調(diào)用某個(gè)函數(shù),并將里面的元素作為函數(shù)的參數(shù):
template <typename F> // F is the type of the function/functorvoid ForEach(F functor) const { for ( const ListNode<E> * node = Head(); node != NULL; node = node->next() ) { functor(node->element()); }}
因此,我們關(guān)注一下:environments_.ForEach(SetUpEnvironment),其實(shí)是迭代調(diào)用了SetUpEnvironment函數(shù):
static void SetUpEnvironment(Environment* env) { env->SetUp(); }
最終調(diào)用了我們定義的SetUp()函數(shù)。
再看看test_cases_.ForEach(TestCase::RunTestCase)的TestCase::RunTestCase實(shí)現(xiàn):
static void RunTestCase(TestCase * test_case) { test_case->Run(); }
再看TestCase的Run實(shí)現(xiàn):
void TestCase::Run() { if (!should_run_) return; internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); impl->set_current_test_case(this); UnitTestEventListenerInterface * const result_printer = impl->result_printer(); result_printer->OnTestCaseStart(this); impl->os_stack_trace_getter()->UponLeavingGTest(); // 哈!SetUpTestCases事件在這里調(diào)用 set_up_tc_(); const internal::TimeInMillis start = internal::GetTimeInMillis(); // 嗯,前面分析的一個(gè)TestCase對(duì)應(yīng)多個(gè)TestInfo,因此,在這里迭代對(duì)TestInfo調(diào)用RunTest方法 test_info_list_->ForEach(internal::TestInfoImpl::RunTest); elapsed_time_ = internal::GetTimeInMillis() - start; impl->os_stack_trace_getter()->UponLeavingGTest(); // TearDownTestCases事件在這里調(diào)用 tear_down_tc_(); result_printer->OnTestCaseEnd(this); impl->set_current_test_case(NULL);}
第二種事件機(jī)制又浮出我們眼前,非常興奮??梢钥闯?,SetUpTestCases和TearDownTestCaess是在一個(gè)TestCase之前和之后調(diào)用的。接著看test_info_list_->ForEach(internal::TestInfoImpl::RunTest):
static void RunTest(TestInfo * test_info) { test_info->impl()->Run();}哦?TestInfo也有一個(gè)impl?看來(lái)我們之前漏掉了點(diǎn)東西,和UnitTest很類(lèi)似,TestInfo內(nèi)部也有一個(gè)主管各種實(shí)現(xiàn)的類(lèi),那就是TestInfoImpl,它在TestInfo的構(gòu)造函數(shù)中創(chuàng)建了出來(lái)(還記得前面講的TestInfo的創(chuàng)建過(guò)程嗎?):
TestInfo::TestInfo(const char* test_case_name, const char* name, const char* test_case_comment, const char* comment, internal::TypeId fixture_class_id, internal::TestFactoryBase* factory) { impl_ = new internal::TestInfoImpl(this, test_case_name, name, test_case_comment, comment, fixture_class_id, factory);}
因此,案例的執(zhí)行還得看TestInfoImpl的Run()方法,同樣,我簡(jiǎn)化一下,只列出關(guān)鍵部分的代碼:
void TestInfoImpl::Run() { // ...
UnitTestEventListenerInterface* const result_printer = impl->result_printer(); result_printer->OnTestStart(parent_); // 開(kāi)始計(jì)時(shí) const TimeInMillis start = GetTimeInMillis(); Test* test = NULL; __try { // 我們的對(duì)象工廠,使用CreateTest()生成Test對(duì)象 test = factory_->CreateTest(); } __except(internal::UnitTestOptions::GTestShouldProcessSEH( GetExceptionCode())) { AddExceptionThrownFailure(GetExceptionCode(), "the test fixture's constructor"); return; }
// 如果Test對(duì)象創(chuàng)建成功 if (!Test::HasFatalFailure()) {
// 調(diào)用Test對(duì)象的Run()方法,執(zhí)行測(cè)試案例
test->Run(); } // 執(zhí)行完畢,刪除Test對(duì)象 impl->os_stack_trace_getter()->UponLeavingGTest(); delete test; test = NULL; // 停止計(jì)時(shí) result_.set_elapsed_time(GetTimeInMillis() - start); result_printer->OnTestEnd(parent_);}
上面看到了我們前面講到的對(duì)象工廠fatory,通過(guò)fatory的CreateTest()方法,創(chuàng)建Test對(duì)象,然后執(zhí)行案例又是通過(guò)Test對(duì)象的Run()方法:
void Test::Run() { if (!HasSameFixtureClass()) return; internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); impl->os_stack_trace_getter()->UponLeavingGTest(); __try { // Yeah!每個(gè)案例的SetUp事件在這里調(diào)用 SetUp(); } __except(internal::UnitTestOptions::GTestShouldProcessSEH( GetExceptionCode())) { AddExceptionThrownFailure(GetExceptionCode(), "SetUp()"); } // We will run the test only if SetUp() had no fatal failure. if (!HasFatalFailure()) { impl->os_stack_trace_getter()->UponLeavingGTest(); __try { // 哈哈??!千辛萬(wàn)苦,我們定義在TEST宏里的東西終于被調(diào)用了! TestBody(); } __except(internal::UnitTestOptions::GTestShouldProcessSEH( GetExceptionCode())) { AddExceptionThrownFailure(GetExceptionCode(), "the test body"); } } impl->os_stack_trace_getter()->UponLeavingGTest(); __try { // 每個(gè)案例的TearDown事件在這里調(diào)用 TearDown(); } __except(internal::UnitTestOptions::GTestShouldProcessSEH( GetExceptionCode())) { AddExceptionThrownFailure(GetExceptionCode(), "TearDown()"); }}
上面的代碼里非常極其以及特別的興奮的看到了執(zhí)行測(cè)試案例的前后事件,測(cè)試案例執(zhí)行TestBody()的代碼。仿佛整個(gè)gtest的流程在眼前一目了然了。
四、總結(jié)
本文通過(guò)分析TEST宏和RUN_ALL_TEST宏,了解到了整個(gè)gtest運(yùn)作過(guò)程,可以說(shuō)整個(gè)過(guò)程簡(jiǎn)潔而優(yōu)美。之前讀《代碼之美》,感觸頗深,現(xiàn)在讀過(guò)gtest代碼,再次讓我感觸深刻。記得很早前,我對(duì)設(shè)計(jì)的理解是“功能越強(qiáng)大越好,設(shè)計(jì)越復(fù)雜越好,那樣才顯得?!?,漸漸得,我才發(fā)現(xiàn),簡(jiǎn)單才是最好。我曾總結(jié)過(guò)自己寫(xiě)代碼的設(shè)計(jì)原則:功能明確,設(shè)計(jì)簡(jiǎn)單。了解了gtest代碼后,猛然發(fā)現(xiàn)gtest不就是這樣嗎,同時(shí)gtest也給了我很多驚喜,因此,我對(duì)gtest的評(píng)價(jià)是:功能強(qiáng)大,設(shè)計(jì)簡(jiǎn)單,使用方便。
總結(jié)一下gtest里的幾個(gè)關(guān)鍵的對(duì)象:
1. UnitTest 單例,總管整個(gè)測(cè)試,包括測(cè)試環(huán)境信息,當(dāng)前執(zhí)行狀態(tài)等等。
2. UnitTestImpl UnitTest內(nèi)部具體功能的實(shí)現(xiàn)者。
3. Test 我們自己編寫(xiě)的,或通過(guò)TEST,TEST_F等宏展開(kāi)后的Test對(duì)象,管理著測(cè)試案例的前后事件,具體的執(zhí)行代碼TestBody。
4. TestCase 測(cè)試案例對(duì)象,管理著基于TestCase的前后事件,管理內(nèi)部多個(gè)TestInfo。
5. TestInfo 管理著測(cè)試案例的基本信息,包括Test對(duì)象的創(chuàng)建方法。
6. TestInfoImpl TestInfo內(nèi)部具體功能的實(shí)現(xiàn)者 。