在《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宏时讲解通过“偷梁换柱”实现的虚方法。
如此整个调度的流程就分析清楚了。
暂无评论内容