Google Test(GTest)使用方法和源码解析——自动调度机制分析
? ? ? ? 在《Google Test(GTest)使用方法和源碼解析——概況?》一文中,我們簡單介紹了下GTest的使用和特性。從這篇博文開始,我們將深入代碼,研究這些特性的實現。(轉載請指明出于breaksoftware的csdn博客)
測試用例的自動保存
? ? ? ?當使用一組宏構成測試代碼后,我們并沒有發現調用它們的地方。GTest框架實際上是通過這些宏,將我們的邏輯保存到類中,然后逐個去執行的。我們先查看TEST宏的實現
#define GTEST_TEST(test_case_name, test_name)\GTEST_TEST_(test_case_name, test_name, \::testing::Test, ::testing::internal::GetTestTypeId())// Define this macro to 1 to omit the definition of TEST(), which
// is a generic name and clashes with some other libraries.
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif
? ? ? ? 可見它只是對GTEST_TEST_宏的再次封裝。GTEST_TEST_宏不僅要求傳入測試用例和測試實例名,還要傳入Test類名和其ID。我們將GTEST_TEST_的實現拆成三段分析
// Helper macro for defining tests.
#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));\
};\
\
? ? ? ? 首先使用宏GTEST_TEST_CLASS_NAME_生成類名。該類暴露了一個空的默認構造函數、一個私有的虛函數TestBody、一個靜態變量test_info_和一個私有的賦值運算符(將運算符=私有化,限制類對象的賦值和拷貝行為)。
? ? ? ? 靜態變量test_info的作用非常有意思,它利用”靜態變量在程序運行前被初始化“的特性,搶在main函數執行之前,執行一段代碼,從而有機會將測試用例放置于一個固定的位置。這個是”自動“保存測試用例的本質所在。
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\::test_info_ =\::testing::internal::MakeAndRegisterTestInfo(\#test_case_name, #test_name, NULL, NULL, \::testing::internal::CodeLocation(__FILE__, __LINE__), \(parent_id), \parent_class::SetUpTestCase, \parent_class::TearDownTestCase, \new ::testing::internal::TestFactoryImpl<\GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
? ? ? ? 我們先跳過這段代碼,看完GTEST_TEST_宏的實現,其最后一行是
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
? ? ? ? 這行要在類外提供TestBody函數的實現。我們要注意下,這個只是函數的一部分,即它只是包含了函數返回類型、函數名,而真正的函數實體是在TEST宏之后的{}內的,如
TEST(FactorialTest, Zero) {EXPECT_EQ(1, Factorial(0));
}
? ? ? ?這段代碼最后應該如下,它實際上是測試邏輯的主體。
……
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() {EXPECT_EQ(1, Factorial(0));
}
? ? ? ? 可以說TEST宏的寫法只是一種類函數的寫法,而實際它“偷梁換柱”,實現了測試的實體。
? ? ? ? 我們再看下test_info_的初始化邏輯,它調用了::testing::internal::MakeAndRegisterTestInfo函數。我們先關注下最后一個參數,它是一個模板類,模板是當前類名。同時從名字上看,它也是一個工廠類。該類繼承于TestFactoryBase,并實現了CreateTest方法——它只是new出了一個模板類對象,并返回
template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {public:virtual Test* CreateTest() { return new TestClass; }
};
? ? ? ??MakeAndRegisterTestInfo函數的實現也非常簡單:它new出一個TestInfo類對象,并調用UnitTestImpl單例的AddTestInfo方法,將其保存起來。
TestInfo* MakeAndRegisterTestInfo(const char* test_case_name,const char* name,const char* type_param,const char* value_param,CodeLocation code_location,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,code_location, fixture_class_id, factory);GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);return test_info;
}
? ? ? ? AddTestInfo試圖通過測試用例名等信息獲取測試用例,然后調用測試用例對象去新增一個測試特例——test_info。這樣我們在此就將測試用例和測試特例的關系在代碼中找到了關聯。
GetTestCase(test_info->test_case_name(),test_info->type_param(),set_up_tc,tear_down_tc)->AddTestInfo(test_info);
? ? ? ? 但是如果第一次調用TEST宏,是不會有測試用例類的,那么其中新建測試用例對象,并保存到UnitTestImpl類單例對象的test_cases_中的邏輯是在GetTestCase函數實現中
TestCase* UnitTestImpl::GetTestCase(const char* test_case_name,const char* type_param,Test::SetUpTestCaseFunc set_up_tc,Test::TearDownTestCaseFunc tear_down_tc) {// Can we find a TestCase with the given name?const std::vector<TestCase*>::const_iterator test_case =std::find_if(test_cases_.begin(), test_cases_.end(),TestCaseNameIs(test_case_name));if (test_case != test_cases_.end())return *test_case;// No. Let's create one.TestCase* const new_test_case =new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc);// Is this a death test case?if (internal::UnitTestOptions::MatchesFilter(test_case_name,kDeathTestCaseFilter)) {++last_death_test_case_;test_cases_.insert(test_cases_.begin() + last_death_test_case_,new_test_case);} else {test_cases_.push_back(new_test_case);}test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));return new_test_case;
}
? ? ? ? 正如我們所料,在沒有找到測試實例對象指針的情況下,新建了一個TestCase測試用例對象,并將其指針保存到了test_cases_中。如此我們就解釋了,測試用例是如何被保存的了。
測試特例的保存
? ? ? ? 接著上例的分析,如下代碼將測試特例信息通過TestCase類的AddTestInfo方法保存起來
GetTestCase(test_info->test_case_name(),test_info->type_param(),set_up_tc,tear_down_tc)->AddTestInfo(test_info);
? ? ? ? 其中AddTestInfo的實現如下
void TestCase::AddTestInfo(TestInfo * test_info) {test_info_list_.push_back(test_info);test_indices_.push_back(static_cast<int>(test_indices_.size()));
}
? ? ? ? 可見test_info_list_中保存了測試特例信息。
調度的實現
? ? ? ? 在之前的測試代碼中,我們并沒有發現main函數。但是C/C++語言要求程序必須要有程序入口,那Main函數呢?其實GTest為了讓我們可以更簡單的使用它,為我們編寫了一個main函數,它位于src目錄下gtest_main.cc文件中
GTEST_API_ int main(int argc, char **argv) {printf("Running main() from gtest_main.cc\n");testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
? ? ? ? Makefile文件編譯了該文件,并將其鏈接到可執行文件中。這樣我們的程序就有了入口。那么這個main函數又是如何將執行流程引到我們的代碼中的呢?代碼之前了無秘密。短短的這幾行,只有04行才可能是我們的代碼入口。(03行將程序入參傳遞給了Gtest庫,從而實現了《Google Test(GTest)使用方法和源碼解析——概況》中所述的“選擇性測試”)。很顯然,它的名字——RUN_ALL_TESTS也暴露了它的功能。我們來看下其實現
inline int RUN_ALL_TESTS() {return ::testing::UnitTest::GetInstance()->Run();
}
? ? ? ? 它最終調用了UnitTest類的單例(GetInstance)的Run方法。UnitTest類的單例是個很重要的對象,它在源碼中各處可見,它是連接各個邏輯的重要一環。我們再看下Run方法的核心實現(去除平臺差異后)
return internal::HandleExceptionsInMethodIfSupported(impl(),&internal::UnitTestImpl::RunAllTests,"auxiliary test code (environments or event listeners)") ? 0 : 1;
? ? ? ? impl()方法返回了一個UnitTestImpl對象指針impl_,它是在UniTes類的構造函數中生成的(HandleExceptionsInMethodIfSupported函數見《Google Test(GTest)使用方法和源碼解析——概況》分析)
UnitTest::UnitTest() {impl_ = new internal::UnitTestImpl(this);
}
? ? ? ? UnitTestImpl類的RunAllTest方法中,核心的調度代碼只有這幾行
for (int test_index = 0; test_index < total_test_case_count(); test_index++) {GetMutableTestCase(test_index)->Run();
}
? ? ? ??GetMutableTestCase方法逐個返回UnitTestImpl對象成員變量test_cases_中的元素——各個測試用例對象指針,然后調用測試用例的Run方法。
std::vector<TestCase*> test_cases_;
? ? ? ? 測試用例類TestCase的Run方法邏輯也是類似的,它將逐個獲取其下的測試特例信息,并調用其Run方法
for (int i = 0; i < total_test_count(); i++) {GetMutableTestInfo(i)->Run();}
? ? ? ? 測試特例的Run方法其核心是
Test* const test = internal::HandleExceptionsInMethodIfSupported(factory_, &internal::TestFactoryBase::CreateTest,"the test fixture's constructor");if ((test != NULL) && !Test::HasFatalFailure()) {test->Run();}
? ? ? ? 它通過構造函數傳入的工廠類對象指針調用其重載的CreateTest方法,new出TEST宏中定義的使用GTEST_TEST_CLASS_NAME_命名(用例名_實例名_TEST)的類(之后稱測試用例特例類)的對象指針,然后調用測試用例特例類的父類中的Run方法。由于測試用例特例類繼承::testing::Test類后,并沒有重載其Run方法,所以其調用的還是Test類的Run方法,而Test類的Run方法實際上只是調用了測試用例特例類重載了的TestBody方法
internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, "the test body");
? ? ? ? 而TestBody就是我們之前在分析TEST宏時講解通過“偷梁換柱”實現的虛方法。
? ? ? ? 如此整個調度的流程就分析清楚了。
總結
以上是生活随笔為你收集整理的Google Test(GTest)使用方法和源码解析——自动调度机制分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google Test(GTest)使用
- 下一篇: Google Test(GTest)使用