借助于 Googletest 测试框架,我们只需编写测试用例代码,并定义简单的 main()
函数,编译之后并运行即可以把我们的测试用例跑起来。(更详细的内容可参考 Googletest 入门)。但 main()
函数调用 RUN_ALL_TESTS()
时,是如何找到并运行我们编写的测试用例代码的呢?本文尝试找寻 Googletest 框架背后隐藏的这些秘密。(代码分析基于 git@github.com:google/googletest.git
的 commit 2134e3fd857d952e03ce76064fad5ac6e9036104 的版本。)
从 TEST 和 TEST_F 说起
googletest/googletest/include/gtest/gtest.h
中 TEST
和 TEST_F
两个宏的定义如下:
TEST
和 TEST_F
两个宏最终都借助于 GTEST_TEST_
宏完成其工作。
googletest/googletest/include/gtest/internal/gtest-internal.h
中 GTEST_TEST_
宏的定义如下:
GTEST_TEST_
宏定义一个类,它为定义的类实现如下特性:
- 类名:类名由测试套件名、测试用例名和字符串
Test
3 部分组成,不同部分用下划线分割。如TEST_F(FooTest, InitializesCorrectly)
和TEST(FooTest, InitializesCorrectly)
定义的类名为FooTest_InitializesCorrectly_Test
。 - 父类:
GTEST_TEST_
宏定义的类继承自传给它的parent_class
参数类。对于TEST_F
宏而言,就是测试套件名,对于TEST
则是::testing::Test
类。 GTEST_TEST_
宏为它定义的类定义了一个空的默认构造函数。GTEST_TEST_
宏为它定义的类定义并初始化类型为::testing::TestInfo* const
的静态成员变量test_info_
,这个指针变量指向由::testing::internal::MakeAndRegisterTestInfo()
函数创建的::testing::TestInfo
类型对象。::testing::internal::MakeAndRegisterTestInfo()
函数还会将测试用例的信息注册给 Google Test。GTEST_TEST_
宏为它定义的类定义TestBody()
虚函数覆盖父类的对应函数,函数的函数体正是我们编写的测试用例代码,这也就是为什么我们在测试用例代码体中打断点时,看到调用栈,代码是位于名为TestBody()
的函数中的原因。- 通过宏
GTEST_DISALLOW_COPY_AND_ASSIGN_
实现类对象的禁用拷贝构造和赋值。
::testing::internal::MakeAndRegisterTestInfo()
函数定义(googletest/googletest/src/gtest.cc
)如下:
测试用例的注册
在通过 TEST
和 TEST_F
宏定义测试用例时,这些宏实际上将会为测试用例定义一个类,这个类包含一个静态成员变量,在这个静态成员变量初始化时,在 ::testing::internal::MakeAndRegisterTestInfo()
函数中向 Google Test 注册测试用例,具体而言,通过 ::testing::internal::UnitTestImpl::AddTestInfo()
函数注册测试用例,该函数在 googletest/googletest/src/gtest-internal-inl.h
中定义,定义如下:
::testing::internal::UnitTestImpl::AddTestInfo()
函数根据测试套件名字等参数获得测试套件;然后将测试用例添加进测试套件中。
googletest/googletest/src/gtest.cc
中用于获得测试套件的 ::testing::internal::UnitTestImpl::GetTestSuite()
函数定义如下:
::testing::internal::UnitTestImpl::GetTestSuite()
函数:
- 根据传入的测试套件名在测试套件 vector 中查找测试套件,如果找到就返回。
- 在测试套件 vector 中没有找到对应的测试套件,创建一个测试套件
TestSuite
对象。 - 测试套件名字与 death test 测试套件名规则匹配时,创建的测试套件被放在测试套件 vector 的前面,并在执行时先执行。
- 测试套件名字与 death test 测试套件名规则不匹配时,创建的测试套件被放在测试套件 vector 的后面,并在执行时后执行。
向 TestSuite
添加测试用例的 TestSuite::AddTestInfo()
函数定义(googletest/googletest/src/gtest.cc
)如下:
测试用例的执行
在 main()
函数中,通过调用 testing::InitGoogleTest(&argc, argv)
和 RUN_ALL_TESTS()
执行所有的测试用例。testing::InitGoogleTest(&argc, argv)
主要用于解析命令行参数,如 filter 等,这里不再详细分析。googletest/googletest/include/gtest/gtest.h
中的 RUN_ALL_TESTS()
函数定义如下:
RUN_ALL_TESTS()
函数调用 googletest/googletest/src/gtest.cc
中定义的 UnitTest::Run()
函数执行所有的测试用例:
internal::HandleExceptionsInMethodIfSupported()
是 Google test 定义的一个用于执行类的成员函数的函数,这里不再详细分析这个函数的实现。UnitTest::Run()
函数实际上通过 googletest/googletest/src/gtest.cc
中定义的 internal::UnitTestImpl::RunAllTests()
函数执行所有的测试用例:
internal::UnitTestImpl::RunAllTests()
函数通过 internal::UnitTestImpl::GetMutableSuiteCase()
函数拿到测试套件 TestSuite
并执行其 Run()
函数:
TestSuite::Run()
函数通过 GetMutableTestInfo()
函数获得 TestInfo
并执行其 Run()
函数,TestInfo::Run()
函数定义如下:
TestInfo::Run()
函数创建测试用例类对象,并执行其 Run()
函数。Test::Run()
执行测试用例定义的 SetUp()
,测试用例主体 TestBody()
函数,和 TearDown()
函数:
测试用例的调用执行过程大概是这样的:
RUN_ALL_TESTS()
-> UnitTest::Run()
-> UnitTestImpl::RunAllTests()
-> TestSuite::Run()
-> TestInfo::Run()
-> Test::Run()
-> Test::TestBody()
。
Done。