GoogleTest 源码分析:断言监听器与结果上报
断言系统负责把“代码检查失败”转换为结构化结果。监听器系统负责把结构化结果输出到终端、XML 或自定义报告。
断言宏的机制
EXPECT_EQ(a, b) 不是普通函数调用。它通过宏捕获表达式文本、文件名和行号,并把比较结果转换为 AssertionResult 或失败对象。失败时框架会记录:
- 源文件路径。
- 行号。
- 断言表达式。
- 期望值与实际值。
- 用户追加的消息。
- 失败类型,致命或非致命。
ASSERT_* 与 EXPECT_* 的关键区别是控制流。致命断言失败后会从当前函数返回,因此 ASSERT_* 只能用于返回 void 的测试函数或辅助函数中。
断言失败如何进入 TestResult
断言失败可以理解为生成一个 TestPartResult:
EXPECT_EQ
-> 比较表达式
-> 成功:不记录失败
-> 失败:构造 TestPartResult
-> 写入当前 TestResult
-> 通知 OnTestPartResult
TestPartResult 的类型通常可以分为:
| 类型 | 含义 |
|---|---|
| 成功 | 当前检查通过 |
| 非致命失败 | EXPECT_* 失败,测试继续 |
| 致命失败 | ASSERT_* 或 FAIL(),当前函数返回 |
| 跳过 | GTEST_SKIP(),当前测试标记为跳过 |
致命断言为什么能中断当前函数
ASSERT_* 是宏,不是普通函数。它可以在宏展开中插入 return 语句,从当前测试函数或辅助函数返回。简化理解如下:
#define ASSERT_TRUE(condition) \
if (!(condition)) { \
/* 记录致命失败 */ \
return; \
}
真实实现比这个复杂得多,因为它还要处理表达式文本、流式消息、文件行号和平台差异。但控制流本质就是:致命失败会让当前返回 void 的函数提前结束。
因此不要在返回非 void 的普通函数中直接使用 ASSERT_*:
int BuildValue() {
ASSERT_TRUE(Ready()); // 不推荐:ASSERT_* 需要能直接 return
return 42;
}
更好的写法是让辅助函数返回状态,测试体中使用 ASSERT_*:
std::optional<int> BuildValue() {
if (!Ready()) {
return std::nullopt;
}
return 42;
}
TEST(BuildTest, BuildsValueWhenReady) {
auto value = BuildValue();
ASSERT_TRUE(value.has_value());
EXPECT_EQ(*value, 42);
}
为什么断言宏可以输出左右值
专用断言会把用户传入的表达式文本和值都传给内部比较函数。例如 EXPECT_EQ(a, b) 可以同时知道:
- 表达式名字:
a和b。 - 表达式求值后的具体值。
- 比较失败时使用的比较关系。
这也是 EXPECT_EQ(a, b) 比 EXPECT_TRUE(a == b) 更推荐的原因。
事件监听器
GoogleTest 使用事件监听器解耦测试执行和结果输出。默认监听器会把结果打印到终端,也可以添加自定义监听器。
class BriefListener : public ::testing::EmptyTestEventListener {
public:
void OnTestEnd(const ::testing::TestInfo& info) override {
if (info.result()->Failed()) {
std::cout << info.test_suite_name() << "." << info.name() << " failed\n";
}
}
};
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::UnitTest::GetInstance()->listeners().Append(new BriefListener);
return RUN_ALL_TESTS();
}
常见事件顺序可以简化为:
OnTestProgramStart
-> OnTestIterationStart
-> OnTestSuiteStart
-> OnTestStart
-> OnTestPartResult
-> OnTestEnd
-> OnTestSuiteEnd
-> OnTestIterationEnd
-> OnTestProgramEnd
事件监听器适合用于:
- 输出自定义日志。
- 对接内部测试平台。
- 收集失败测试的额外上下文。
- 改造默认终端输出。
不要在监听器里改变被测业务状态。监听器应主要负责观察和报告,避免让测试执行结果依赖监听器副作用。
XML 输出的来源
使用下面参数时:
./unit_tests --gtest_output=xml:test_report.xml
GoogleTest 会通过内部监听器把测试结果写成 XML。XML 中通常包含:
- 测试套件名。
- 测试名。
- 执行时间。
- 失败信息。
- 跳过信息。
- 用户通过
RecordProperty添加的属性。
示例:
TEST(BuildInfoTest, RecordsCompiler) {
RecordProperty("compiler", "gcc");
SUCCEED();
}
RecordProperty 适合记录环境信息,但不应承载测试判断逻辑。
线程与当前测试结果
GoogleTest 需要知道当前断言应该写入哪个测试结果中。内部实现会维护当前运行状态,使断言失败能够被归入当前 TestInfo 对应的 TestResult。
如果在测试中创建线程,需要格外小心:
TEST(ThreadTest, WorkerReportsResult) {
std::thread worker([] {
EXPECT_EQ(1 + 1, 2);
});
worker.join();
}
简单场景通常可以工作,但复杂工程中更推荐让线程返回结果,在主测试线程中断言:
TEST(ThreadTest, ChecksWorkerResultInMainThread) {
std::promise<int> promise;
auto future = promise.get_future();
std::thread worker([&promise] {
promise.set_value(2);
});
EXPECT_EQ(future.get(), 2);
worker.join();
}
这样测试失败位置和控制流更清晰,也更容易避免线程生命周期问题。
GoogleMock 与 GoogleTest 的关系
GoogleMock 构建在 GoogleTest 之上。常见的 EXPECT_THAT、匹配器和 mock 对象最终仍然依赖 GoogleTest 的测试运行、失败记录和事件上报机制。
EXPECT_THAT(values, ::testing::ElementsAre(1, 2, 3));
可以把 GoogleMock 理解为增强了“如何表达期望”和“如何验证交互”,而 GoogleTest 负责“如何组织和运行测试”。