ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Google Test 样例全解析:从基础断言到 Listener 扩展(miniblink49 仓库 v8_7_5 内嵌 gtest 实践指南)

Google Test 样例全解析:从基础断言到 Listener 扩展(miniblink49 仓库 v8_7_5 内嵌 gtest 实践指南) Google Test 样例全解析从基础断言到 Listener 扩展miniblink49 仓库 v8_7_5 内嵌 gtest 实践指南【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49Google Testgtest是 Google 出品的 C 单元测试框架在 miniblink49 仓库中随 V8 7.5 一并内嵌于v8_7_5/testing/gtest用于支撑 V8 及周边代码的回归测试。本文以官方文档 V1_7_Samples.md 为核心骨架结合仓库内 10 个官方样例的完整源码系统讲解 gtest 从入门到进阶的全部核心特性如何编写第一个单元测试、如何使用测试夹具Test Fixture、如何实现类型参数化与值参数化测试以及如何通过 Listener API 定制输出、检查内存泄漏。读完本文你将具备直接阅读、运行并复刻这套官方样例的能力并将其迁移到自己的 C 工程中。一、文档与样例总览一条完整的 gtest 学习路径官方文档 V1_7_Samples.md 篇幅精炼其价值在于为 samples 目录 下 10 个“well-commented”注释详尽的样例建立索引。这些样例由浅入深恰好构成一条完整的 gtest 特性学习路径样例主题核心 APISample #1函数单元测试的基本三步TEST、EXPECT_EQ、EXPECT_TRUESample #2含多成员函数类的单元测试EXPECT_STREQSample #3测试夹具Test FixtureTEST_F、SetUp/TearDownSample #4基础用例补充断言只求值一次EXPECT_EQ副作用Sample #5通过派生子夹具复用测试夹具夹具继承、TearDown中断言Sample #6类型参数化测试两代写法TYPED_TEST/TYPED_TEST_PSample #7值参数化测试TEST_P、TestWithParam、GetParamSample #8Combine()组合多组参数INSTANTIATE_TEST_CASE_PCombineSample #9Listener API 定制控制台输出 反射 APIEmptyTestEventListener、UnitTestSample #10Listener API 实现简易内存泄漏检查器自定义事件监听器注意本文所述 API 形态如TEST_CASE系命名以仓库内嵌的 1.7 版本为准与新版本 gtest1.8 改用TEST_SUITE命名存在差异下文会同步指出新旧命名对应关系。二、Sample #1单元测试“三步走”掌握最小可运行测试Sample #1 展示如何为一个普通 C 函数编写单元测试被被测对象是 sample1.h / sample1.cc 中的Factorial()阶乘与IsPrime()素数判定。源码注释把写作流程归纳为清晰的 1-2-3Step 1包含头文件测试代码需要包含被测目标的声明同时必须引入框架头文件gtest/gtest.h实际路径为 v8_7_5/testing/gtest/include/gtest/gtest.h#include limits.h #include sample1.h #include gtest/gtest.hStep 2用 TEST 宏定义测试TEST宏接收两个参数测试用例名test case name与测试名test name随后在花括号内书写测试逻辑TEST(FactorialTest, Negative) { EXPECT_EQ(1, Factorial(-5)); EXPECT_EQ(1, Factorial(-1)); EXPECT_GT(Factorial(-10), 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 case如FactorialTest用于保持测试代码的组织性命名约束用例名与测试名都必须是合法 C 标识符且不要在其中使用下划线_——下划线是 gtest 内部命名空间预留的顺序无关性gtest 保证每个测试恰好被执行一次但不保证执行顺序因此测试必须写成结果不依赖执行顺序的形式。断言宏的选择EXPECT_EQ 优于 EXPECT_TRUE针对IsPrime()样例分别覆盖负数、平凡值0、1、2、3与正数输入TEST(IsPrimeTest, Negative) { EXPECT_FALSE(IsPrime(-1)); EXPECT_FALSE(IsPrime(-2)); EXPECT_FALSE(IsPrime(INT_MIN)); } TEST(IsPrimeTest, Trivial) { EXPECT_FALSE(IsPrime(0)); EXPECT_FALSE(IsPrime(1)); EXPECT_TRUE(IsPrime(2)); EXPECT_TRUE(IsPrime(3)); } TEST(IsPrimeTest, Positive) { EXPECT_FALSE(IsPrime(4)); EXPECT_TRUE(IsPrime(5)); EXPECT_FALSE(IsPrime(6)); EXPECT_TRUE(IsPrime(23)); }注释中特别解释了EXPECT_EQ(expected, actual)与EXPECT_TRUE((expected) (actual))的等价关系二者行为等价但EXPECT_EQ在失败时会同时打印期望值与实际值对调试极有帮助因此优先使用而EXPECT_TRUE接受任意布尔表达式通用性更强。此外还有带ASSERT_前缀的兄弟宏见下节失败时会直接终止当前测试。Step 3调用 RUN_ALL_TESTS()所有测试由RUN_ALL_TESTS()统一驱动。样例通过链接 src/gtest_main.cc 获得现成的main()——该文件内部即包含调用RUN_ALL_TESTS()的入口函数运行全部已定义测试、打印结果成功返回 0、失败返回 1。注意你不需要手工注册任何测试RUN_ALL_TESTS()宏会自动感知所有已定义测试。三、Sample #2 与 Sample #4类的成员函数测试与断言副作用为类的每个成员函数建立测试Sample #2 演示对 sample2.h / sample2.cc 中MyString类的单元测试。惯例是为类的每个成员函数各建一个测试保持组织清晰TEST(MyString, DefaultConstructor) { const MyString s; EXPECT_STREQ(NULL, s.c_string()); EXPECT_EQ(0u, s.Length()); } TEST(MyString, ConstructorFromCString) { const MyString s(kHelloString); EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); EXPECT_EQ(sizeof(kHelloString)/sizeof(kHelloString[0]) - 1, s.Length()); } TEST(MyString, CopyConstructor) { const MyString s1(kHelloString); const MyString s2 s1; EXPECT_EQ(0, strcmp(s2.c_string(), kHelloString)); } TEST(MyString, Set) { MyString s; s.Set(kHelloString); EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); s.Set(s.c_string()); // 输入指针与内部指针相同也必须正确工作 EXPECT_EQ(0, strcmp(s.c_string(), kHelloString)); s.Set(NULL); EXPECT_STREQ(NULL, s.c_string()); }这里引入了两个新知识点EXPECT_STREQ用于 C 字符串const char*的相等断言而非EXPECT_EQ后者比较的是指针值NULL的类型陷阱源码注释专门解释了一个隐蔽问题——若直接写EXPECT_EQ(NULL, ...)由于NULL被宏定义为整数 0编译器会把格式化函数按int推导而 gcc 3.4 会因“NULL 应作为指针而非 int”而告警。根因是 C 无法区分整数 0 与空指针常量因此需要写成static_castconst char*(NULL)或改用EXPECT_STREQ。断言中的副作用EXPECT_EQ 恰好求值一次Sample #4 是一个极简补充样例测试 sample4.h / sample4.cc 中的计数器类TEST(Counter, Increment) { Counter c; EXPECT_EQ(0, c.Increment()); EXPECT_EQ(1, c.Increment()); EXPECT_EQ(2, c.Increment()); }注释明确指出EXPECT_EQ()的实参恰好被求值一次因此实参可以安全地带有副作用此处Increment()每次调用都会修改状态三次断言依次验证 0→1→2 的递增行为。这为在断言内书写有副作用的表达式提供了依据。四、Sample #3测试夹具Test Fixture与 TEST_F当多个测试需要共享初始化对象与公共子例程时若直接复制粘贴会产生大量重复代码。Sample #3 展示 gtest 的解法——测试夹具被测对象是 sample3-inl.h 中的Queueint模板队列。定义夹具从 testing::Test 派生class QueueTest : public testing::Test { protected: virtual void SetUp() { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } static int Double(int n) { return 2*n; } void MapTester(const Queueint * q) { const Queueint * const new_q q-Map(Double); ASSERT_EQ(q-Size(), new_q-Size()); for (const QueueNodeint * n1 q-Head(), * n2 new_q-Head(); n1 ! NULL; n1 n1-next(), n2 n2-next() ) { EXPECT_EQ(2 * n1-element(), n2-element()); } delete new_q; } Queueint q0_; Queueint q1_; Queueint q2_; };要点如下夹具类必须从testing::Test公开派生成员建议声明为protected以便子类访问SetUp()在每个测试运行前被调用用于初始化共享变量本例向队列预置数据无初始化需求时可省略TearDown()在每个测试结束后被调用用于清理无清理需求时可省略样例中以注释形式给出空实现夹具中可以定义供测试复用的子例程如MapTester与共享对象如q0_、q1_、q2_。用 TEST_F 编写夹具测试有夹具后测试改用TEST_F定义TEST_F(QueueTest, DefaultConstructor) { EXPECT_EQ(0u, q0_.Size()); } TEST_F(QueueTest, Dequeue) { int * n q0_.Dequeue(); EXPECT_TRUE(n NULL); n q1_.Dequeue(); ASSERT_TRUE(n ! NULL); EXPECT_EQ(1, *n); EXPECT_EQ(0u, q1_.Size()); delete n; n q2_.Dequeue(); ASSERT_TRUE(n ! NULL); EXPECT_EQ(2, *n); EXPECT_EQ(1u, q2_.Size()); delete n; } TEST_F(QueueTest, Map) { MapTester(q0_); MapTester(q1_); MapTester(q2_); }两个关键设计原理源码注释揭示了 gtest 的两条核心设计哲学理解它们才能写出正确测试夹具是“代码共享”而非“数据共享”每个测试都会获得一份全新的、独立的夹具副本一个测试修改的数据不会传递给下一个测试。原因在于测试必须相互独立且可重复——一个测试不应因另一个测试的失败而失败若两个测试确实依赖彼此产生的数据它们本该合并成一个更大的测试。断言宏只能在测试内使用EXPECT_*、FAIL等宏在技术上是Test类的成员函数gtest 打印失败信息时需要知道“当前是哪个测试”。因此在全局函数中无法使用它们——这也是测试子例程必须放进夹具的原因MapTester内部使用了ASSERT_EQ/EXPECT_EQ正因它位于夹具类内部。注意ASSERT_EQ/ASSERT_TRUE与EXPECT_*的区别ASSERT_*失败时立即终止当前测试Dequeue中先ASSERT_TRUE(n ! NULL)再解引用*n避免空指针崩溃而EXPECT_*失败仅记录错误并继续执行。五、Sample #5通过子夹具派生让多个用例共享同一套前置/后置逻辑一个夹具在 gtest 1.7 中只能被一个测试用例使用TEST_F的第一个参数即夹具类名。当多个测试用例需要相同或相似的前置/后置逻辑时例如“所有测试都不得慢于 5 秒”“GUI 库测试不得泄漏字体/画刷等系统资源”Sample #5 给出的模式是把公共逻辑放进超类夹具super fixture再为每个用例派生专属子夹具。定义超类夹具QuickTestclass QuickTest : public testing::Test { protected: virtual void SetUp() { start_time_ time(NULL); } virtual void TearDown() { const time_t end_time time(NULL); EXPECT_TRUE(end_time - start_time_ 5) The test took too long.; } time_t start_time_; };超类夹具本身不绑定任何测试用例没有名为QuickTest的用例这是允许的。它的TearDown()中直接使用断言检查运行时长——在 SetUp/TearDown 中同样可以使用断言这是本例的亮点之一。派生子夹具并逐级调用 SetUpclass IntegerFunctionTest : public QuickTest { // 无需额外逻辑体为空 }; TEST_F(IntegerFunctionTest, Factorial) { EXPECT_EQ(1, Factorial(-5)); ... EXPECT_EQ(40320, Factorial(8)); } TEST_F(IntegerFunctionTest, IsPrime) { EXPECT_FALSE(IsPrime(-1)); ... EXPECT_TRUE(IsPrime(23)); }IntegerFunctionTest直接继承QuickTest于是它内部的所有TEST_F测试自动获得“5 秒超时检查”。而另一个用例QueueTest需要额外的队列数据则在自己的SetUp()中先显式调用超类SetUp()再做附加初始化class QueueTest : public QuickTest { protected: virtual void SetUp() { QuickTest::SetUp(); // 先完成超类设置 q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // TearDown 默认继承 QuickTest::TearDown()无需重写 Queueint q0_; Queueint q1_; Queueint q2_; };注释最后指出夹具的派生层次在 gtest 中没有深度限制可以从QueueTest再派生但实践中不宜过深以免混乱。六、Sample #6类型参数化测试——让同一套测试跑遍所有实现当需要验证同一接口的多个实现是否满足共同约束时接口测试interface testsSample #6 展示了两种做法。被测接口 prime_tables.h 提供PrimeTable抽象基类及其两个实现OnTheFlyPrimeTable实时计算素数与PreCalculatedPrimeTable预计算素数表。准备夹具类模板与工厂函数template class T PrimeTable* CreatePrimeTable(); template PrimeTable* CreatePrimeTableOnTheFlyPrimeTable() { return new OnTheFlyPrimeTable; } template PrimeTable* CreatePrimeTablePreCalculatedPrimeTable() { return new PreCalculatedPrimeTable(10000); } template class T class PrimeTableTest : public testing::Test { protected: PrimeTableTest() : table_(CreatePrimeTableT()) {} virtual ~PrimeTableTest() { delete table_; } PrimeTable* const table_; // 通过基类接口而非具体实现测试 };注意夹具通过基类指针持有被测对象这更贴近真实使用场景也避免了“实现类成员函数遮蔽基类同名函数”这类陷阱。写法一Typed Test类型在编写时就已知#if GTEST_HAS_TYPED_TEST using testing::Types; typedef TypesOnTheFlyPrimeTable, PreCalculatedPrimeTable Implementations; TYPED_TEST_CASE(PrimeTableTest, Implementations); TYPED_TEST(PrimeTableTest, ReturnsFalseForNonPrimes) { EXPECT_FALSE(this-table_-IsPrime(-5)); EXPECT_FALSE(this-table_-IsPrime(0)); EXPECT_FALSE(this-table_-IsPrime(1)); EXPECT_FALSE(this-table_-IsPrime(4)); EXPECT_FALSE(this-table_-IsPrime(6)); EXPECT_FALSE(this-table_-IsPrime(100)); } TYPED_TEST(PrimeTableTest, ReturnsTrueForPrimes) { ... } TYPED_TEST(PrimeTableTest, CanGetNextPrime) { ... } #endif // GTEST_HAS_TYPED_TESTTYPED_TEST_CASE(夹具名, 类型列表)声明用例并指定类型参数TYPED_TEST定义测试。gtest 会为类型列表中的每个类型各执行一遍所有TYPED_TEST无需重复书写。模板世界中的两个注意事项可用TypeParam指代当前类型参数且访问夹具成员时必须显式写this-C 模板两阶段查找的要求。写法二Type-Parameterized Test类型可在将来扩展当编写测试时还不知道全部待测类型例如接口作者希望第三方日后实现接口并复用测试则用_P后缀P 代表 parameterized 或 pattern的宏将测试写成可注册、可实例化的“测试模式”#if GTEST_HAS_TYPED_TEST_P template class T class PrimeTableTest2 : public PrimeTableTestT {}; TYPED_TEST_CASE_P(PrimeTableTest2); TYPED_TEST_P(PrimeTableTest2, ReturnsFalseForNonPrimes) { ... } TYPED_TEST_P(PrimeTableTest2, ReturnsTrueForPrimes) { ... } 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); // 类型列表 #endif // GTEST_HAS_TYPED_TEST_P相比 typed test这种写法多出两步必须用REGISTER_TYPED_TEST_CASE_P枚举测试再用INSTANTIATE_TYPED_TEST_CASE_P绑定具体类型。好处是测试模式通常放在.h文件中任何实现方#include后即可实例化甚至可在同一程序中多次实例化每个实例名会成为用例名的一部分、可用于测试过滤。七、Sample #7值参数化测试——参数是“值”而非“类型”sample7_unittest.cc 演示值参数化测试每个测试携带一个参数该参数是被测实现的指针此处是工厂函数指针。与 Sample #6 的“类型参数”形成对照。#if GTEST_HAS_PARAM_TEST using ::testing::TestWithParam; using ::testing::Values; typedef PrimeTable* CreatePrimeTableFunc(); PrimeTable* CreateOnTheFlyPrimeTable() { return new OnTheFlyPrimeTable(); } template size_t max_precalculated PrimeTable* CreatePreCalculatedPrimeTable() { return new PreCalculatedPrimeTable(max_precalculated); } class PrimeTableTest : public TestWithParamCreatePrimeTableFunc* { public: virtual ~PrimeTableTest() { delete table_; } virtual void SetUp() { table_ (*GetParam())(); } virtual void TearDown() { delete table_; table_ NULL; } protected: PrimeTable* table_; }; TEST_P(PrimeTableTest, ReturnsFalseForNonPrimes) { ... } TEST_P(PrimeTableTest, ReturnsTrueForPrimes) { ... } TEST_P(PrimeTableTest, CanGetNextPrime) { ... } INSTANTIATE_TEST_CASE_P( OnTheFlyAndPreCalculated, PrimeTableTest, Values(CreateOnTheFlyPrimeTable, CreatePreCalculatedPrimeTable1000)); #else TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {} #endif // GTEST_HAS_PARAM_TEST要点归纳夹具继承TestWithParamT在测试体/SetUp/TearDown中通过GetParam()获取当前参数值测试用TEST_P定义参数化测试必须实例化才能运行INSTANTIATE_TEST_CASE_P(实例名, 用例名, Values(...))其中Values列出参数值集合实例化可以在其他翻译单元完成也可多次实例化通用原则为避免测试相互影响被测对象应在每个测试中创建/销毁本样例在SetUp中创建、TearDown中删除而非跨测试复用平台兼容当编译器不支持参数化测试GTEST_HAS_PARAM_TEST未定义时样例用条件编译输出一个DummyTest占位测试以保持gtest_main被链接否则 MSVC 会报 LNK1561 入口点缺失错误。这种条件编译手法同样用于 Sample #6、#8。八、Sample #8Combine() 组合参数穷举多标志组合sample8_unittest.cc 解决“被测代码依赖多个全局标志变量”的测试场景Combine()负责生成这些标志的全部可能组合每个测试拿到一组组合作为参数。样例构造了一个HybridPrimeTable见 prime_tables.h它同时持有OnTheFlyPrimeTable与PreCalculatedPrimeTable两个实现根据阈值择优查询且在低内存场景下可通过force_on_the_fly标志跳过预计算表的创建class HybridPrimeTable : public PrimeTable { public: HybridPrimeTable(bool force_on_the_fly, int max_precalculated) : on_the_fly_impl_(new OnTheFlyPrimeTable), precalc_impl_(force_on_the_fly ? NULL : new PreCalculatedPrimeTable(max_precalculated)), max_precalculated_(max_precalculated) {} ... virtual bool IsPrime(int n) const { if (precalc_impl_ ! NULL n max_precalculated_) return precalc_impl_-IsPrime(n); else return on_the_fly_impl_-IsPrime(n); } ... };随后用Combine将Bool()真假与Range/Values类型的参数生成笛卡尔积#if GTEST_HAS_COMBINE INSTANTIATE_TEST_CASE_P( MeanAndMedian, HybridPrimeTableTest, Combine(Values(true, false), // force_on_the_fly Values(1, 10))); // max_precalculated #endif // GTEST_HAS_COMBINECombine()的价值在于无需手工书写for循环嵌套即可一次性覆盖多个布尔标志与取值范围的所有组合特别适合验证“受多个开关影响”的类。该宏同样受GTEST_HAS_COMBINE条件编译保护。九、Sample #9Listener API 定制输出 反射 API 检查结果前 8 个样例关注“怎么写测试”Sample #9 转向“测试框架的可扩展性”。它演示两件事用Listener API实现替代的控制台输出用UnitTest 反射 API枚举用例与测试、检查其结果。实现自定义监听器class TersePrinter : public EmptyTestEventListener { private: virtual void OnTestProgramStart(const UnitTest /* unit_test */) {} 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); } 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) { fprintf(stdout, *** Test %s.%s ending.\n, test_info.test_case_name(), test_info.name()); fflush(stdout); } };EmptyTestEventListener是监听器接口的空实现基类只需按需覆写事件方法即可无需实现全部回调。可用的事件包括OnTestProgramStart/End整个测试程序开始/结束、OnTestStart/End单个测试开始/结束、OnTestPartResult每次断言产生结果时。TestInfo反射对象提供test_case_name()、name()等方法TestPartResult提供failed()、file_name()、line_number()、summary()等结果信息。在 main 中装配监听器int main(int argc, char **argv) { InitGoogleTest(argc, argv); bool terse_output false; if (argc 1 strcmp(argv[1], --terse_output) 0 ) terse_output true; else printf(%s\n, Run this program with --terse_output to change the way it prints its output.); UnitTest unit_test *UnitTest::GetInstance(); if (terse_output) { // 移除默认监听器注册 TersePrinter TestEventListeners listeners unit_test.listeners(); delete listeners.Release(listeners.default_result_printer()); listeners.Append(new TersePrinter); } return RUN_ALL_TESTS(); }核心手法是通过UnitTest::GetInstance()拿到全局单例listeners.Release(...)移除默认结果打印器listeners.Append(new TersePrinter)挂载自定义监听器。同时借助反射 APIUnitTest、TestCase、TestInfo等枚举并检查结果。运行时用--terse_output开关切换输出模式。样例附带三个演示测试PrintsMessage、Succeeds、Fails其中Fails故意EXPECT_EQ(1, 2)失败用于演示替代格式的失败信息。十、Sample #10用 Listener API 打造简易内存泄漏检查器Sample #10 把 Listener API 应用到实际工程问题——内存泄漏检测。思路朴素而有效统计每个测试运行前后某类对象的存活数量若测试结束后数量反而增加即判定泄漏。可统计分配次数的 Water 类class Water { public: void* operator new(size_t allocation_size) { allocated_; return malloc(allocation_size); } void operator delete(void* block, size_t /* allocation_size */) { allocated_--; free(block); } static int allocated() { return allocated_; } private: static int allocated_; }; int Water::allocated_ 0;通过重载operator new/operator delete维护静态计数器allocated_即可知道任意时刻存活的Water对象数量。监听器实现泄漏检查class LeakChecker : public EmptyTestEventListener { private: virtual void OnTestStart(const TestInfo /* test_info */) { initially_allocated_ Water::allocated(); } virtual void OnTestEnd(const TestInfo /* test_info */) { int difference Water::allocated() - initially_allocated_; EXPECT_LE(difference, 0) Leaked difference unit(s) of Water!; } int initially_allocated_; };LeakChecker在OnTestStart记录初始存活数在OnTestEnd用EXPECT_LE断言增量不大于 0。源码注释特别提醒除OnTestPartResult外任何事件处理器中都可以使用 gtest 断言产生失败。演示效果TEST(ListenersTest, DoesNotLeak) { Water* water new Water; delete water; } // 当指定 --check_for_leaks 时此测试应当失败 TEST(ListenersTest, LeaksWater) { Water* water new Water; EXPECT_TRUE(water ! NULL); }main()中同样通过--check_for_leaks命令行开关决定是否装配LeakChecker。运行后LeaksWater测试漏掉了delete会因增量大于 0 而失败并报告“Leaked 1 unit(s) of Water!”DoesNotLeak则正常通过。这个不足百行的原型演示了如何以极低成本为工程引入基础的内存泄漏监控。十一、本地运行与源码导航所有样例的编译入口可参考仓库中的构建配置gtest 自身通过 BUILD.gnGN 构建与 CMakeLists.txtCMake 构建组织样例目录 samples 亦含配套构建描述。在支持 CMake 的环境中可配置 gtest 构建并启用gtest_build_samples选项然后运行对应样例可执行文件Sample #9 与 Sample #10 分别支持--terse_output、--check_for_leaks命令行开关用于演示监听器的实际效果。进一步深入研究可参考以下仓库文件框架核心声明include/gtest/gtest.h全部断言宏与TEST/TEST_F宏定义测试入口实现src/gtest_main.ccRUN_ALL_TESTS的调用入口事件监听接口include/gtest/gtest.h 中TestEventListener/EmptyTestEventListener定义被测样例目标samples/prime_tables.h、samples/sample1.cc、samples/sample2.cc、samples/sample3-inl.h、samples/sample4.cc结语从 10 个样例建立 gtest 的完整心智模型回看这 10 个样例它们几乎覆盖了 gtest 1.7 的全部核心机制TEST定义测试、EXPECT_*/ASSERT_*断言、测试夹具与TEST_F、夹具继承复用、typed / type-parameterized / value-parameterized 三种参数化测试、Combine()组合参数以及通过 Listener API 定制输出与检测内存泄漏。其中蕴含的两条设计原则值得贯穿始终测试必须相互独立夹具按测试重建、断言不依赖执行顺序以及优先使用失败信息更丰富的断言EXPECT_EQ优于裸EXPECT_TRUE。无论是为 miniblink49 这类大型 C 工程补充回归测试还是在自己的项目引入单元测试体系这套样例都是最佳起点——官方文档 V1_7_Samples.md 则始终是定位这些样例的快捷索引。【免费下载链接】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),仅供参考
返回列表