Google Test 官方 10 个 C 单元测试样例精解从 TEST 宏到监听器 APIminiblink49 仓库实测【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49本文以 miniblink49 仓库内携带的 Google Test 官方文档 V1_6_Samples.md 为主线逐一对齐其列出的 10 个官方样例samples 目录从函数级断言、类方法测试、test fixture、参数化测试一路讲到监听器 API并结合仓库中的真实源码说明每个宏与 API 的底层用法。读完本文你可以掌握 Google Test 的完整入门路径并直接在自己的 C 项目中复刻这套测试组织方式。适用范围说明以下所有路径与代码均来自当前仓库v8_5_7/testing/gtest/目录这是一份随 v8_5_7 一起携带、文档命名对应 1.6 系列的 Google Test 源码树。所有样例代码、断言语义、构建文件均以仓库实际内容为准。1. 先从官方索引看全貌10 个样例分别教什么官方文档 V1_6_Samples.md 的核心内容是一句话结论If youre like us, youd like to look at some Google Test sample code. The samples folder has a number of well-commented samples showing how to use a variety of Google Test features.——即 samples 目录是一组注释极其详尽的功能演示下面这张对照表完整继承自文档并补充了每个样例对应的被测代码样例文件文档定义的主题配套被测代码sample1_unittest.cc使用 Google Test 测试 C 函数的基本步骤sample1.cc / sample1.hsample2_unittest.cc对一个含多个成员函数的类做更复杂的单测sample2.cc / sample2.hsample3_unittest.cc使用 test fixture测试夹具sample3-inl.hsample4_unittest.cc另一个基础示例强调断言参数只求值一次sample4.cc / sample4.hsample5_unittest.cc通过派生子夹具在多个 test case 中复用同一夹具复用 sample1、sample3 的被测代码sample6_unittest.cc类型参数化测试typed / type-parameterizedprime_tables.hsample7_unittest.cc值参数化测试value-parameterized基础同上sample8_unittest.cc值参数化测试中使用Combine()生成参数组合同上sample9_unittest.cc用 listener API 改造控制台输出用反射 API 检查测试结果—sample10_unittest.cc用 listener API 实现一个简易内存泄漏检测器—文档原句逐一对应为Sample 1 展示基本步骤Sample 2 展示对多成员函数类的单测Sample 3 使用 fixtureSample 4 是另一个基础示例Sample 5 教授如何通过派生子夹具在多个 test case 间复用夹具Sample 6 演示类型参数化测试Sample 7 教授值参数化测试基础Sample 8 展示值参数化测试中的Combine()Sample 9 展示 listener API 与反射 APISample 10 展示用 listener API 实现原始内存泄漏检查。下文按基础断言 → 夹具 → 参数化 → 监听器四条主线逐一精讲。2. Sample 14从零开始写第一个单测2.1 Sample 1函数级单测的三步曲sample1_unittest.cc 的注释明确给出了 Google Test 写测试的三步引入头文件既包含被测代码的头sample1.h也不要忘记gtest/gtest.h——框架的所有断言与宏都由它声明。用TEST宏定义测试TEST接受两个参数——test case 名称与测试名称随后在一对大括号里写测试逻辑用EXPECT_*系列宏断言成功或失败。调用RUN_ALL_TESTS()这一步通常不用自己写 main而是链接 src/gtest_main.cc——该文件提供了一个现成的main()内部调用RUN_ALL_TESTS()并返回 0全部通过或 1存在失败。被测对象是 sample1.cc 中的两个函数Factorial(int n)返回 n!负 n 按 1 处理与IsPrime(int n)素性判断用 试除到平方根 的经典算法。对应测试代码如下// Tests factorial of negative numbers. TEST(FactorialTest, Negative) { EXPECT_EQ(1, Factorial(-5)); EXPECT_EQ(1, Factorial(-1)); EXPECT_GT(Factorial(-10), 0); } // Tests factorial of 0. TEST(FactorialTest, Zero) { EXPECT_EQ(1, Factorial(0)); } TEST(FactorialTest, Positive) { EXPECT_EQ(1, Factorial(1)); EXPECT_EQ(2, Factorial(2)); EXPECT_EQ(6, Factorial(3)); EXPECT_EQ(40320, Factorial(8)); } TEST(IsPrimeTest, Negative) { EXPECT_FALSE(IsPrime(-1)); EXPECT_FALSE(IsPrime(-2)); EXPECT_FALSE(IsPrime(INT_MIN)); }这里蕴含三个关键设计决策均可在样例注释的TechnicalDetails中找到原始解释测试分组逻辑相关的测试放进同一个 test case如FactorialTest、IsPrimeTest。test case 名与测试名都必须是合法 C 标识符且官方建议不要使用下划线。执行语义Google Test 保证每个测试恰好执行一次但不保证执行顺序因此测试必须互不依赖、结果与顺序无关。EXPECT_EQ优于裸EXPECT_TRUEEXPECT_EQ(expected, actual)等价于EXPECT_TRUE((expected) (actual))但失败时会把期望值与实际值同时打印出来极大方便调试EXPECT_TRUE更通用可接收任意布尔表达式。2.2 Sample 2类的多成员函数测试sample2_unittest.cc 针对 sample2.h 中一个自实现的极简字符串类MyString含默认构造、C 字符串构造、拷贝构造、Set、Length、c_string等成员进行测试。官方建议每个成员方法对应一个测试本例便是这一组织方式的示范TEST(MyString, DefaultConstructor) { const MyString s; // EXPECT_STREQ(NULL, s.c_string()); EXPECT_EQ(0u, s.Length()); } TEST(MyString, Set) { MyString s; s.Set(kHelloString); EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); // Set should work when the input pointer is the same as the one // already in the MyString object. s.Set(s.c_string()); // Can we set the MyString to NULL? s.Set(NULL); EXPECT_STREQ(NULL, s.c_string()); }本样例最有价值的两个技术点EXPECT_STREQ用于 C 风格字符串比较的是内容而非指针失败时打印两个字符串本身。NULL的类型陷阱注释详细记录了一个 gcc 3.4 时代的经典警告——直接写EXPECT_EQ(NULL, ...)时由于NULL被宏定义为整数0编译器会按int选择格式化函数而 gcc 认为NULL应作指针用而报警告。根源是 C 没有区分整数 0与空指针常量。因此对指针判空应显式写成static_castconst char*(NULL)或用EXPECT_STREQ。2.3 Sample 3test fixture——共享初始化与清理逻辑sample3_unittest.cc 引入 Google Test 的核心抽象test fixture测试夹具。被测代码是 sample3-inl.h 中的Queueint模板队列类。使用方法从testing::Test派生子类把共享对象和辅助函数放进protected区然后class QueueTest : public testing::Test { protected: // SetUp() 在每条测试运行前被调用用于初始化共享变量 virtual void SetUp() { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // TearDown() 在每条测试运行后被调用若无清理工作可省略 // 供测试复用的辅助函数 void MapTester(const Queueint* q) { /* ... */ } // 测试要使用的共享变量 Queueint q0_; Queueint q1_; Queueint q2_; }; // 使用夹具时用 TEST_F 而非 TEST TEST_F(QueueTest, DefaultConstructor) { EXPECT_EQ(0u, q0_.Size()); } TEST_F(QueueTest, Dequeue) { int* n q1_.Dequeue(); ASSERT_TRUE(n ! NULL); // 致命断言失败立即中止当前测试 EXPECT_EQ(1, *n); delete n; }三个必须记住的语义均来自样例TechnicalDetails代码共享数据不共享每条测试都会获得一份全新的夹具实例SetUp()/TearDown()在每条测试前后执行。一个测试修改的数据绝不应被另一个测试观察到——这正是测试必须独立、可重复的保障。断言宏需要当前测试上下文EXPECT_TRUE、FAIL等宏实际调用的是Test类的成员函数因此不能在全局函数中使用这正解释了为什么要用夹具承载辅助子过程。ASSERT_*与EXPECT_*的分工ASSERT_TRUE/ASSERT_EQ等致命断言一旦失败立即中止当前测试防止在空指针上继续操作EXPECT_*非致命失败则让测试继续跑完用于收集尽可能多的失败信息。上例先ASSERT_TRUE(n ! NULL)再EXPECT_EQ(1, *n)就是标准的安全写法。2.4 Sample 4断言参数只求值一次sample4_unittest.cc 被测对象是 sample4.h 的计数器类Counter。核心测试只有一段重点在注释揭示的语义TEST(Counter, Increment) { Counter c; // EXPECT_EQ() evaluates its arguments exactly once, so they // can have side effects. EXPECT_EQ(0, c.Increment()); EXPECT_EQ(1, c.Increment()); EXPECT_EQ(2, c.Increment()); }EXPECT_EQ()会对参数精确求值一次因此参数可以安全地携带副作用如Increment()自增并返回旧值连续断言能可靠验证 0→1→2 的计数轨迹。3. Sample 5夹具继承——一处实现多处复用sample5_unittest.cc 解决一个现实问题一个夹具只能被一个 test case 使用因为TEST_F的第一个参数必须与夹具类同名。当多个 test case 需要相同的前置/后置逻辑时官方做法是super fixture超类夹具 子夹具派生。样例中的业务场景是希望保证每条测试都在约 5 秒内结束超时即失败。实现方式是把计时逻辑写进超类夹具QuickTestclass QuickTest : public testing::Test { protected: // SetUp() 在测试开始前立即执行这里记录起始时间 virtual void SetUp() { start_time_ time(NULL); } // TearDown() 在测试结束后立即执行这里检查耗时 virtual void TearDown() { const time_t end_time time(NULL); // 注意SetUp() 和 TearDown() 里同样可以使用断言 EXPECT_TRUE(end_time - start_time_ 5) The test took too long.; } time_t start_time_; };然后派生子夹具让不同 test case 各自复用// 子夹具 1无额外逻辑空实现即可 class IntegerFunctionTest : public QuickTest { }; // 子夹具 2既有 QuickTest 的超时检查又有自己的共享对象 class QueueTest : public QuickTest { protected: virtual void SetUp() { QuickTest::SetUp(); // 先初始化超类夹具 q1_.Enqueue(1); // 再做本夹具的额外初始化 q2_.Enqueue(2); q2_.Enqueue(3); } Queueint q0_, q1_, q2_; };随后TEST_F(IntegerFunctionTest, Factorial)、TEST_F(IntegerFunctionTest, IsPrime)、TEST_F(QueueTest, Dequeue)各自继承超时约束。注释还指出夹具继承层级可以继续加深例如再从一个派生夹具派生Google Test 不限制深度但实践中不宜过深以免难以理解。这个模式非常适合 GUI 库等场景——例如统一检查每个测试是否泄漏字体、画刷等系统资源。4. Sample 68参数化测试的三件套参数化测试要解决的共性问题对同一接口的多个实现或同一逻辑的多组输入重复执行同一组断言。三个样例围绕 prime_tables.h 中PrimeTable接口的两个实现展开OnTheFlyPrimeTable运行时实时判素数与PreCalculatedPrimeTable预计算素数表构造参数为表容量。4.1 Sample 6类型参数化测试typed tests 与 type-parameterized testssample6_unittest.cc 展示了两种做法适用场景不同方式一typed tests已知全部类型。当你编写测试时就已经确定要测哪些类型用TYPED_TEST_CASE声明类型列表、TYPED_TEST定义测试typedef TypesOnTheFlyPrimeTable, PreCalculatedPrimeTable Implementations; TYPED_TEST_CASE(PrimeTableTest, Implementations); TYPED_TEST(PrimeTableTest, ReturnsTrueForPrimes) { EXPECT_TRUE(this-table_-IsPrime(2)); EXPECT_TRUE(this-table_-IsPrime(3)); // ... }Google Test 会自动把每个TYPED_TEST对类型列表中的每个类型各跑一遍。注意由于进入模板世界访问夹具成员必须显式写this-样例注释强调这是 C 模板依赖查找规则使然。方式二type-parameterized tests未来类型未知。如果你是接口作者希望别人后续实现也能复用你的测试就用TYPED_TEST_CASE_PTYPED_TEST_P定义测试模式然后用REGISTER_TYPED_TEST_CASE_P登记、用INSTANTIATE_TYPED_TEST_CASE_P实例化TYPED_TEST_CASE_P(PrimeTableTest2); TYPED_TEST_P(PrimeTableTest2, CanGetNextPrime) { /* ... */ } // 登记模式中定义的所有测试名 REGISTER_TYPED_TEST_CASE_P(PrimeTableTest2, ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime); // 用实例名 类型列表把抽象模式变成真实测试 typedef TypesOnTheFlyPrimeTable, PreCalculatedPrimeTable PrimeTableImplementations; INSTANTIATE_TYPED_TEST_CASE_P(OnTheFlyAndPreCalculated, PrimeTableTest2, PrimeTableImplementations);实例名会成为 test case 名的一部分可用于测试过滤同一模式可以在同一程序里多次实例化用不同实例名区分。两者均受特性宏GTEST_HAS_TYPED_TEST/GTEST_HAS_TYPED_TEST_P保护。4.2 Sample 7值参数化测试value-parameterized testssample7_unittest.cc 的适用场景是同一套测试要作用于一组值参数。这里参数被设计为指向工厂函数的指针CreatePrimeTableFunc*每个参数值对应一种PrimeTable实现class PrimeTableTest : public TestWithParamCreatePrimeTableFunc* { public: virtual void SetUp() { table_ (*GetParam())(); } // 用参数创建被测对象 virtual void TearDown() { delete table_; table_ NULL; } protected: PrimeTable* table_; }; TEST_P(PrimeTableTest, ReturnsFalseForNonPrimes) { EXPECT_FALSE(table_-IsPrime(-5)); EXPECT_FALSE(table_-IsPrime(0)); // ... } // 把测试绑定到一组参数值上 INSTANTIATE_TEST_CASE_P( OnTheFlyAndPreCalculated, PrimeTableTest, Values(CreateOnTheFlyPrimeTable, CreatePreCalculatedPrimeTable1000));要点夹具继承TestWithParamT测试体内用GetParam()取当前参数参数化测试用TEST_P定义用INSTANTIATE_TEST_CASE_P实例化。通用规则样例注释原文为防止一个测试影响后续测试应每条测试独立创建、销毁被测对象而不是复用本例即把创建放在SetUp()、销毁放在TearDown()。测试通过PrimeTable基类接口而非具体实现类来断言更贴近真实调用场景也能避开子类方法遮蔽基类同名重载这类陷阱。4.3 Sample 8Combine()生成参数全组合sample8_unittest.cc 针对一个更复杂的被测对象HybridPrimeTable组合了预计算表的快与实时计算的灵活并支持低内存模式下禁用预计算表。它有两个构造参数bool force_on_the_fly与int max_precalculated需要覆盖两者的全组合using ::testing::TestWithParam; using ::testing::Bool; using ::testing::Values; using ::testing::Combine; class PrimeTableTest : public TestWithParam ::testing::tuplebool, int { protected: virtual void SetUp() { bool force_on_the_fly ::testing::get0(GetParam()); int max_precalculated ::testing::get1(GetParam()); table_ new HybridPrimeTable(force_on_the_fly, max_precalculated); } // ... }; INSTANTIATE_TEST_CASE_P( OnTheFlyAndPreCalculated, PrimeTableTest, Combine(Bool(), Values(1, 10, 100)));Combine()会对每个参数发生器Bool()产生 true/falseValues(1, 10, 100)产生三个整数做笛卡尔积本例共生成 2×36 组参数测试体内用::testing::get0(GetParam())/get1取出各分量注释提到待 C 风格指南允许后可用std::tr1::tie一次性解包。该功能受GTEST_HAS_COMBINE宏保护。5. Sample 910监听器 API 的两大实战5.1 Sample 9自定义控制台输出 反射 API 检查结果sample9_unittest.cc 展示两条高级 APIlistener API继承::testing::EmptyTestEventListener按需重写事件回调所有回调都在测试进程的各个生命周期节点被触发class TersePrinter : public EmptyTestEventListener { private: // 整个测试程序开始前 virtual void OnTestProgramStart(const UnitTest) {} // 整个测试程序结束后 virtual void OnTestProgramEnd(const UnitTest unit_test) { fprintf(stdout, TEST %s\n, unit_test.Passed() ? PASSED : FAILED); fflush(stdout); } // 单条测试开始 virtual void OnTestStart(const TestInfo test_info) { fprintf(stdout, *** Test %s.%s starting.\n, test_info.test_case_name(), test_info.name()); fflush(stdout); } // 一条断言失败或 SUCCEED() 被调用时 virtual void OnTestPartResult(const TestPartResult test_part_result) { fprintf(stdout, %s in %s:%d\n%s\n, test_part_result.failed() ? *** Failure : Success, test_part_result.file_name(), test_part_result.line_number(), test_part_result.summary()); fflush(stdout); } // 单条测试结束 virtual void OnTestEnd(const TestInfo test_info) { /* ... */ } };通过TestEventListeners把自定义监听器注册进UnitTest::GetInstance()即可替换默认输出为极简风格。同时该样例还演示了UnitTest 反射 API通过UnitTest、TestCase、TestInfo等类型枚举所有 test case 与测试、检查其运行结果。注意main()中应使用InitGoogleTest初始化框架。5.2 Sample 10用监听器实现内存泄漏检测sample10_unittest.cc 是 listener API 最经典的实战原始泄漏检查器。思路分三层追踪被测对象给类Water重载operator new/operator delete用静态计数器allocated_记录存活实例数class Water { public: void* operator new(size_t allocation_size) { allocated_; return malloc(allocation_size); } void operator delete(void* block, size_t) { allocated_--; free(block); } static int allocated() { return allocated_; } private: static int allocated_; };监听器比对前后差值LeakChecker在OnTestStart记录初始存活数在OnTestEnd计算差值若差值为正则用断言报告泄漏class LeakChecker : public EmptyTestEventListener { private: virtual void OnTestStart(const TestInfo) { initially_allocated_ Water::allocated(); } virtual void OnTestEnd(const TestInfo) { int difference Water::allocated() - initially_allocated_; EXPECT_LE(difference, 0) Leaked difference unit(s) of Water!; } int initially_allocated_; };两条对比例证TEST(ListenersTest, DoesNotLeak)new 后 delete通过与TEST(ListenersTest, LeaksWater)new 后不 delete携带--check_for_leaks命令行标志运行时失败。样例注释特别提醒除OnTestPartResult外的任何事件回调里都可以安全地使用 Google Test 断言。6. 如何构建与运行这些样例这些样例不是孤立文件而是完整 gtest 源码树的一部分仓库为其提供了多套构建入口被测代码与测试文件全部位于 v8_5_7/testing/gtest/samples框架实现位于 v8_5_7/testing/gtest/src如 gtest-all.cc、gtest-death-test.cc公开头文件位于 include/gtest。顶层 CMakeLists.txt 与 Makefile.amautotools都定义了样例目标另有msvc/与xcode/工程目录可分别在 WindowsVisual Studio与 macOSXcode下打开构建scripts/目录还提供了生成单文件版 gtest 的融合脚本。具体构建步骤以仓库根目录的 README.md 及对应构建文件为准。若想脱离现成工程手动编译只需把被测源文件如sample1.cc与对应*_unittest.cc一起编译并链接 gtest 库与 src/gtest_main.cc 提供的main()该main()内部调用RUN_ALL_TESTS()程序返回 0 表示全部通过返回 1 表示存在失败。提醒样例普遍使用#if GTEST_HAS_PARAM_TEST、#if GTEST_HAS_TYPED_TEST、#if GTEST_HAS_COMBINE等特性宏包裹参数化测试代码编译环境是否启用这些特性以 include/gtest 中的实际配置为准。7. 小结一条清晰的 Google Test 学习路线把官方索引的 10 个样例串起来正好是一条由浅入深的能力曲线Sample 12TEST宏 EXPECT_*/ASSERT_*断言族覆盖函数与类两种被测对象理解测试用例分组与断言只求值一次。Sample 35test fixtureSetUp/TearDown/TEST_F解决初始化与清理的复用夹具继承super fixture把公共约束如超时、资源泄漏批量施加到多个 test case。Sample 68typed tests、type-parameterized tests、value-parameterized tests 与Combine()覆盖同一套断言 × 多实现/多参数的全部形态。Sample 910listener API 与反射 API把测试框架从跑断言升级为可编程的测试基础设施——自定义输出、实现泄漏检查器只是两个起点。无论你是刚接触 Google Test 的新手还是想复用这套模式为 miniblink49 这样的大型 C 项目编写结构化测试都可以直接以 v8_5_7/testing/gtest/samples 目录为活教材每个文件注释详尽、可直接编译运行是比任何教程都更贴近真实代码的官方样例。【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考