Skip to content

GoogleTest 源码分析:参数化死亡测试与调试

参数化测试、类型参数化测试和死亡测试都建立在普通测试注册与运行机制之上。它们看起来更复杂,本质上是扩展了“如何生成测试实例”和“如何执行测试体”。

参数化测试的源码思路

参数化测试比普通测试多了一层“参数生成器”和“实例化”。

class ParserTest : public ::testing::TestWithParam<std::string> {
};

TEST_P(ParserTest, AcceptsValidText) {
    EXPECT_FALSE(GetParam().empty());
}

INSTANTIATE_TEST_SUITE_P(
    ValidInputs,
    ParserTest,
    ::testing::Values("a", "abc"));

可以从源码机制上理解为:

  1. TEST_P 定义一个带参数的测试模式。
  2. INSTANTIATE_TEST_SUITE_P 提供参数集合。
  3. GoogleTest 根据参数集合生成多个具体测试实例。
  4. 每个测试实例运行时通过 GetParam() 取得当前参数。

运行时名称通常会变成类似:

ValidInputs/ParserTest.AcceptsValidText/0
ValidInputs/ParserTest.AcceptsValidText/1

参数化测试的关键不是复制多份测试代码,而是把“测试逻辑”和“测试数据”分开注册。

类型参数化测试的源码思路

类型参数化测试发生在编译期。它通过模板和类型列表为每个类型生成对应测试。

template <typename T>
class ContainerTest : public ::testing::Test {
};

using TypesToTest = ::testing::Types<int, double>;
TYPED_TEST_SUITE(ContainerTest, TypesToTest);

TYPED_TEST(ContainerTest, DefaultVectorIsEmpty) {
    std::vector<TypeParam> values;
    EXPECT_TRUE(values.empty());
}

可以理解为编译器生成了 int 和 double 两套测试类,然后 GoogleTest 分别注册它们。值参数化测试偏运行期数据展开,类型参数化测试偏编译期类型展开。

参数命名

参数化测试默认使用数字后缀。如果希望失败信息更清楚,可以提供命名函数:

struct ParseCase {
    std::string input;
    bool expected_ok;
};

std::string CaseName(const ::testing::TestParamInfo<ParseCase>& info) {
    return info.param.expected_ok ? "Valid" : "Invalid";
}

INSTANTIATE_TEST_SUITE_P(
    TextCases,
    ParserTest,
    ::testing::Values(ParseCase{"abc", true}, ParseCase{"", false}),
    CaseName);

命名函数返回值需要是合法的测试名片段,通常只使用字母、数字和下划线。

死亡测试的运行机制

死亡测试用于验证一段代码会导致进程退出,例如 assert、abort、非法参数触发的终止。它不能简单地在当前测试进程中执行,因为一旦当前进程真的退出,整个测试程序也会结束。

因此死亡测试通常采用类似下面的策略:

父进程
  -> 创建子进程或隔离执行环境
  -> 子进程执行待验证语句
  -> 父进程捕获退出码和 stderr
  -> 检查退出行为是否符合预期
  -> 检查错误输出是否匹配正则

示例:

void CrashWhenNull(int* pointer) {
    assert(pointer != nullptr);
}

TEST(PointerDeathTest, DiesWhenPointerIsNull) {
    EXPECT_DEATH(CrashWhenNull(nullptr), "pointer != nullptr");
}

死亡测试的注意点:

  • 待测语句会在隔离环境中执行,不要依赖父进程中的复杂共享状态。
  • 死亡测试通常比普通单元测试慢。
  • 匹配字符串来自标准错误输出,不同平台或编译模式可能不同。
  • NDEBUG 会关闭 assert,这会影响基于 assert 的死亡测试。

死亡测试风格

GoogleTest 支持通过参数控制死亡测试风格:

./unit_tests --gtest_death_test_style=fast
./unit_tests --gtest_death_test_style=threadsafe
风格 说明
fast 通常更快,但对多线程上下文要求更谨慎
threadsafe 更重视线程安全,代价是更慢

如果测试程序中已经创建了线程,死亡测试更容易暴露平台相关问题。此时应尽量让死亡测试独立、简单,并避免依赖复杂全局状态。

调试 GoogleTest 本身或测试注册问题

当测试没有按预期运行时,可以按以下顺序排查:

  1. 使用 --gtest_list_tests 确认测试是否注册。
  2. 如果没有注册,检查测试源文件是否加入测试目标。
  3. 如果已注册但不运行,检查 --gtest_filter 和 DISABLED_ 前缀。
  4. 如果运行后崩溃,检查夹具 SetUp、全局环境和静态对象。
  5. 如果只在批量运行失败,检查共享状态和执行顺序依赖。
  6. 如果死亡测试异常,检查平台、正则、assert 是否被 NDEBUG 关闭。

常用调试参数

./unit_tests --gtest_list_tests
./unit_tests --gtest_filter=SuiteName.TestName
./unit_tests --gtest_repeat=100 --gtest_break_on_failure
./unit_tests --gtest_shuffle --gtest_random_seed=123
./unit_tests --gtest_also_run_disabled_tests
参数 适合排查的问题
--gtest_list_tests 测试是否注册
--gtest_filter 单独复现某个失败
--gtest_repeat 偶现失败
--gtest_shuffle 测试顺序依赖
--gtest_random_seed 固定随机顺序
--gtest_break_on_failure 失败时进入调试器

小结

参数化测试关注“一个测试逻辑生成多个实例”,类型参数化测试关注“一个测试逻辑覆盖多个类型”,死亡测试关注“在隔离执行环境中验证异常退出”。它们不是独立体系,而是普通 GoogleTest 注册、运行和结果上报机制的扩展。