Skip to content

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 负责“如何组织和运行测试”。