GoogleTest 源码分析:宏注册与运行流程
GoogleTest 的普通测试并不是运行时扫描出来的,而是在编译期通过宏生成测试类,在静态初始化阶段注册到全局测试表中。
TEST 宏做了什么
表面上看,用户只写了一个函数:
TEST(MathTest, AddWorks) {
EXPECT_EQ(1 + 1, 2);
}
实际上宏大致会展开为:
- 生成一个继承自
::testing::Test的测试类。 - 在类中声明
TestBody()。 - 创建一个静态注册对象。
- 静态初始化时调用注册函数,把测试信息加入全局注册表。
- 运行时由框架创建测试对象并调用
TestBody()。
可以用伪代码理解:
class MathTest_AddWorks_Test : public ::testing::Test {
private:
void TestBody() override {
EXPECT_EQ(1 + 1, 2);
}
static ::testing::TestInfo* const test_info_;
};
::testing::TestInfo* const MathTest_AddWorks_Test::test_info_ =
::testing::internal::MakeAndRegisterTestInfo(
"MathTest",
"AddWorks",
nullptr,
nullptr,
::testing::internal::CodeLocation(__FILE__, __LINE__),
::testing::internal::GetTestTypeId(),
::testing::Test::SetUpTestSuite,
::testing::Test::TearDownTestSuite,
new ::testing::internal::TestFactoryImpl<MathTest_AddWorks_Test>);
这就是为什么 GoogleTest 测试不需要手动放进列表里:注册发生在静态初始化阶段。
TEST_F 与测试夹具如何关联
TEST_F 和 TEST 的主要区别是基类不同。TEST 默认生成的测试类继承自 ::testing::Test,而 TEST_F(FixtureName, TestName) 生成的测试类继承自用户提供的 FixtureName。
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
queue.push(1);
}
std::queue<int> queue;
};
TEST_F(QueueTest, FrontReturnsFirstElement) {
EXPECT_EQ(queue.front(), 1);
}
可以把它理解为下面这种伪代码:
class QueueTest_FrontReturnsFirstElement_Test : public QueueTest {
private:
void TestBody() override {
EXPECT_EQ(queue.front(), 1);
}
static ::testing::TestInfo* const test_info_;
};
因此夹具成员变量能在测试体中直接访问。每个测试执行时,GoogleTest 都会通过工厂创建一个新的测试对象,所以不同 TEST_F 之间默认不会共享普通成员变量。
静态注册的意义和风险
静态注册的好处是用户不需要手动维护测试列表。只要测试源文件被编译并链接进最终测试程序,测试就会出现在注册表中。
但这也带来几个工程注意点:
- 测试源文件如果没有被加入测试目标,就不会注册。
- 静态库中的测试对象如果没有被链接器拉入,测试可能不会出现。
- 不同翻译单元的静态初始化顺序不应该被测试逻辑依赖。
- 测试注册阶段只应保存元数据,不应做昂贵业务初始化。
如果 --gtest_list_tests 看不到某个测试,优先检查 CMake 是否把对应 *_test.cpp 加入了测试可执行文件。
单个测试的运行过程
单个测试的运行大致是:
TestInfo::Run
-> 通知 OnTestStart
-> TestFactory 创建测试对象
-> 执行 SetUp
-> 如果 SetUp 没有致命失败,则执行 TestBody
-> 执行 TearDown
-> 销毁测试对象
-> 统计 TestResult
-> 通知 OnTestEnd
TearDown 通常会执行,即使测试体中出现失败也需要清理资源。写测试夹具时,清理逻辑应放在 TearDown 或 RAII 对象中,而不是依赖测试体最后几行一定能运行。
过滤器与测试选择
GoogleTest 在运行前会根据过滤规则决定哪些测试需要执行。常用参数是:
./unit_tests --gtest_filter=QueueTest.*
./unit_tests --gtest_filter=QueueTest.*:-QueueTest.Slow*
源码层面,测试注册表中仍然保存所有测试;过滤器只是影响运行阶段的选择。简化流程如下:
所有 TestInfo
-> 匹配 positive filter
-> 排除 negative filter
-> 检查 disabled 测试
-> 得到 should_run 标记
默认情况下,名字以 DISABLED_ 开头的测试不会执行。可以通过 --gtest_also_run_disabled_tests 强制执行。
运行入口
如果链接 GTest::gtest_main,默认入口大致等价于:
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
InitGoogleTest 负责解析 GoogleTest 自己的命令行参数,例如过滤器、重复次数、输出格式等。RUN_ALL_TESTS 则进入 UnitTest 单例,开始执行注册表中的测试。
阅读建议
阅读宏注册与运行流程时,可以按下面路径追:
gtest.h
-> TEST / TEST_F 宏
-> MakeAndRegisterTestInfo
-> TestInfo
-> TestFactoryImpl
-> gtest_main.cc
-> RUN_ALL_TESTS
-> UnitTestImpl::RunAllTests