ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

miniblink49 内置 V8 6.7 Google Test 样例指南:从 TEST 到 Listener API 的 10 个官方示例详解

miniblink49 内置 V8 6.7 Google Test 样例指南:从 TEST 到 Listener API 的 10 个官方示例详解 miniblink49 内置 V8 6.7 Google Test 样例指南从 TEST 到 Listener API 的 10 个官方示例详解【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49本文基于 miniblink49 仓库中v8_6_7/testing/gtest/docs/V1_7_Samples.md这份 Google Test 样例索引文档展开逐一解读 samples 目录 下的 10 个官方示例源文件并结合 README.md 中的构建说明讲清如何用 CMake 或 Makefile 编译并运行这些样例。读完后你能掌握 Google Test 的完整核心能力TEST/TEST_F基本用法、测试夹具test fixture与超夹具继承、类型化测试、值参数化测试与Combine()以及用 Listener API 定制输出和实现简易内存泄漏检查器。背景miniblink49 中的 Google Test 在哪里miniblink49 是一个轻量 Blink 内核仓库中按版本内嵌了多套 V8v8_4_5到v8_7_5。其中 v8_6_7/testing/gtest 目录内置了 Google Test 框架的完整源码包括头文件include、实现src、文档docs和样例samples。从 docs 目录 的命名V1_7_Samples.md、V1_7_Primer.md等可以推断这里内置的是 gtest 1.7 一代的 API 与文档本文所有示例代码都以这一版本为准。V1_7_Samples.md 本身是一份样例索引它指出 samples 目录中有大量注释详尽的示例覆盖 Google Test 的各种特性并列出 Sample #1 到 #10 各自演示的功能。下表完整继承该索引并把每个条目映射到仓库中的实际文件编号文件演示内容#1sample1_unittest.cc用 Google Test 测试 C 函数的基本步骤#2sample2_unittest.cc对含多个成员函数的类做较复杂的单元测试#3sample3_unittest.cc使用测试夹具test fixture#4sample4_unittest.cc又一个使用 Google Test 的基础示例#5sample5_unittest.cc通过派生子夹具让多个测试用例复用同一夹具#6sample6_unittest.cc类型参数化测试type-parameterized tests#7sample7_unittest.cc值参数化测试value-parameterized tests入门#8sample8_unittest.cc在值参数化测试中使用Combine()#9sample9_unittest.cc用 Listener API 定制控制台输出并用反射 API 检查测试结果#10sample10_unittest.cc用 Listener API 实现一个简易内存泄漏检查器这些样例依赖被测代码头文件sample1.hFactorial/IsPrime、sample2.hMyString、sample3-inl.hQueue、sample4.hCounter和 prime_tables.h素数表接口及其两种实现。构建与运行样例README.md 给出了完整构建流程三种方式任选其一均可用来编译并运行上述样例。方式一手动 g 编译。设 gtest 所在目录为${GTEST_DIR}在本仓库即v8_6_7/testing/gtest编译框架本体g -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ -pthread -c ${GTEST_DIR}/src/gtest-all.cc ar -rv libgtest.a gtest-all.o-pthread是必需的因为 Google Test 使用线程。再编译你自己的测试文件并与libgtest.a链接g -isystem ${GTEST_DIR}/include -pthread path/to/your_test.cc libgtest.a \ -o your_test方式二make/ 目录下的 Makefile。make/ 提供一个可用 GNU make 的 Makefile它只构建 Google Test 库和一个样例测试。按 README 说明cd ${GTEST_DIR}/make make ./sample1_unittest如果环境不同导致出错README 建议在make/Makefile中按其内嵌说明调整变量。方式三CMake推荐可编译全部样例。gtest 自带 CMakeLists.txt典型流程mkdir mybuild cd mybuild cmake ${GTEST_DIR}如果还想构建 Google Test 的样例即本文讲解的 10 个 sample需要打开gtest_build_samples选项cmake -Dgtest_build_samplesON ${GTEST_DIR}在 *nix 下随后执行make即可Windows Visual Studio 会生成gtest.sln和.vcproj工程Mac OS X Xcode 会生成.xcodeproj。README 同时提到Visual Studio 用户也可以直接使用msvc/目录下的gtest.sln/gtest-md.sln-md后缀对应动态运行时 /MD无后缀对应静态运行时 /MT注意 gtest 与测试代码必须用同一种运行时选项编译不过这些遗留工程已不再积极维护README 更推荐 CMake 或集成到自有构建系统。Sample #1用 TEST 宏测试自由函数的“1-2-3”步骤sample1_unittest.cc 是入门样例其文件头注释把写单元测试概括为三步包含头文件#include limits.h、被测代码头sample1.h以及声明测试框架的gtest/gtest.h见 L46-L48。用TEST宏定义测试TEST(用例名, 测试名)后接花括号内的测试逻辑。在 main() 中调用RUN_ALL_TESTS()本样例没有手写 main而是链接src/gtest_main.cc由其中的 main 调用RUN_ALL_TESTS()运行所有测试、打印结果成功返回 0否则返回 1见 L143-L153。测试体本身非常直观例如测试Factorial()的负数输入TEST(FactorialTest, Negative) { EXPECT_EQ(1, Factorial(-5)); EXPECT_EQ(1, Factorial(-1)); EXPECT_GT(Factorial(-10), 0); }L79-L100样例中的TechnicalDetails注释补充了几个关键知识点测试按测试用例test case分组逻辑相关的测试放进同一用例用例名和测试名都必须是合法 C 标识符且不能包含下划线。EXPECT_EQ(expected, actual)等价于EXPECT_TRUE((expected) (actual))但断言失败时会打印期望值和实际值因此优先使用EXPECT_EQ这类比较宏EXPECT_TRUE接受任意布尔表达式更通用。Google Test 保证每个定义的测试恰好运行一次但不保证执行顺序因此测试之间不能依赖顺序、不能相互影响。Sample #2测试一个多成员函数的类sample2_unittest.cc 测试一个简单的字符串类MyString。文件头注释给出组织建议通常一个方法对应一个测试不强制但有助于保持测试有序必要时再补充额外测试。四个测试分别覆盖默认构造函数L49-L75从 C 字符串构造L80-L85拷贝构造L88-L92Set()方法包括“把输入指针设为对象自身已持有的指针”和“设为 NULL”这类边界场景L95-L109。其中默认构造测试里有一条值得注意的写法EXPECT_STREQ(NULL, s.c_string());注释解释了为什么不用EXPECT_EQEXPECT_EQ需要知道参数类型以便失败时打印值而NULL被#define为0编译器会选用 int 的格式化函数与 gcc 3.4 的指针语义检查冲突而告警。根因是 C 未区分整数 0 和空指针常量字符串比较场景用EXPECT_STREQ即可绕开L52-L72。Sample #3测试夹具 TEST_Fsample3_unittest.cc 引入测试夹具夹具是所有测试共享的公共对象和函数避免在每个测试里重复初始化/清理代码也适合放置测试需要频繁调用的子例程。核心写法从testing::Test派生夹具类用TEST_F(夹具名, 测试名)定义测试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) { /* 用断言校验 q-Map(Double) */ } Queueint q0_; Queueint q1_; Queueint q2_; }; TEST_F(QueueTest, Dequeue) { int * n q0_.Dequeue(); EXPECT_TRUE(n NULL); // ... }L70-L144TechnicalDetails部分阐明了三条设计原则L44-L64测试共享夹具是代码共享而非数据共享——每个测试拿到的都是夹具的一份全新拷贝一个测试修改的数据不会传递给下一个测试这样保证测试独立、可重复。SetUp()在每个测试运行前调用TearDown()在每个测试运行后调用无初始化/清理需求时可省略。EXPECT_TRUE、FAIL等断言宏内部需要知道“当前测试”是谁打印结果时要标明失败属于哪个测试技术上它们调用Test类的成员函数因此不能在全局函数里使用断言——测试子例程必须放在夹具内。样例里的MapTester()就演示了这一点它作为夹具成员使用ASSERT_EQ和EXPECT_EQL96-L111。Sample #4断言参数的求值语义sample4_unittest.cc 很短测试Counter::Increment()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()); }L36-L45这个样例的教学点很具体EXPECT_EQ()对参数恰好求值一次因此参数可以带副作用——这里连续三次Increment()的自增副作用是安全的如果断言宏多次求值返回值就会错乱。Sample #5超夹具——让多个用例复用一套前置逻辑sample5_unittest.cc 解决“多个测试用例想用相同或略有不同夹具”的问题。约束在于定义夹具时同时指定了使用它的测试用例名一个夹具只能服务一个用例。解法是把共享逻辑放进一个超夹具super fixture再让各用例的夹具从它派生。样例的目标是“任何测试超过 5 秒就算失败”。超夹具QuickTest在SetUp()记录开始时间在TearDown()断言耗时class 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_; };L63-L85两个要点其一QuickTest本身没有对应的测试用例这完全合法——它只是给别人继承的基类其二断言宏在SetUp()/TearDown()中同样可用。随后IntegerFunctionTest空派生直接继承“快速”约束与QueueTest在SetUp()里先调QuickTest::SetUp()再初始化队列L144-L167各自从QuickTest派生两个用例的测试就自动受 5 秒时限约束TearDown()默认继承QuickTest::TearDown()无额外清理工作时可省略。文件末尾还说明可以从派生夹具继续派生更深层的夹具Google Test 不限制层级深度但实践中不宜过深以免难以理解L195-L199。Sample #6类型化测试与类型参数化测试sample6_unittest.cc 演示“接口测试”用同一套测试验证同一接口的多个实现这里是被 prime_tables.h 声明的PrimeTable接口的两种实现OnTheFlyPrimeTable与PreCalculatedPrimeTable。Google Test 提供两种复用方式类型化测试typed tests——写测试时已知全部被测类型。先定义工厂函数和夹具类模板L44-L75template class T PrimeTable* CreatePrimeTable(); template PrimeTable* CreatePrimeTableOnTheFlyPrimeTable() { return new OnTheFlyPrimeTable; } template class T class PrimeTableTest : public testing::Test { protected: PrimeTableTest() : table_(CreatePrimeTableT()) {} virtual ~PrimeTableTest() { delete table_; } PrimeTable* const table_; };夹具成员table_声明为基类接口指针而非具体实现类型注释说明这是关键测试应通过基类接口访问实现贴近真实调用场景避免实现类中同名但签名不同的方法“遮蔽”基类方法导致的坑。然后声明用例并定义类型化测试typedef TypesOnTheFlyPrimeTable, PreCalculatedPrimeTable Implementations; TYPED_TEST_CASE(PrimeTableTest, Implementations); TYPED_TEST(PrimeTableTest, ReturnsTrueForPrimes) { EXPECT_TRUE(this-table_-IsPrime(2)); // ... }L94-L132模板世界里访问夹具成员需要显式写this-。Google Test 会为类型列表中的每种类型重复执行每个TYPED_TEST。类型参数化测试type-parameterized tests——写测试时尚不知有哪些实现典型场景你是接口作者希望未来所有实现都满足基础要求。流程多三步template class T class PrimeTableTest2 : public PrimeTableTestT {}; TYPED_TEST_CASE_P(PrimeTableTest2); TYPED_TEST_P(PrimeTableTest2, ReturnsTrueForPrimes) { /* 同上 */ } REGISTER_TYPED_TEST_CASE_P( PrimeTableTest2, ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime); typedef TypesOnTheFlyPrimeTable, PreCalculatedPrimeTable PrimeTableImplementations; INSTANTIATE_TYPED_TEST_CASE_P(OnTheFlyAndPreCalculated, // 实例名 PrimeTableTest2, // 用例名 PrimeTableImplementations); // 类型列表L160-L222要点REGISTER_TYPED_TEST_CASE_P必须枚举测试名把抽象“测试模式”登记下来INSTANTIATE_TYPED_TEST_CASE_P把模式实例化为真实测试实例名会成为用例名的一部分可用于测试过滤器测试模式通常放在.h里任何地方#include后都能实例化甚至同一程序里实例化多次。整个 typed 段由#if GTEST_HAS_TYPED_TEST/#if GTEST_HAS_TYPED_TEST_P宏保护L77、L140因为部分编译器不支持这些特性。Sample #7值参数化测试基础sample7_unittest.cc 展示值参数化value-parameterized测试的入门形态每个测试带一个参数这里是“被测对象的工厂函数指针”每个参数值各跑一轮。文件头注释给出通用原则为防止测试相互影响应为每个测试单独创建/销毁被测对象而不是复用——因此样例定义了工厂函数并在夹具的SetUp()/TearDown()中分配和释放对象typedef PrimeTable* CreatePrimeTableFunc(); PrimeTable* CreateOnTheFlyPrimeTable() { return new OnTheFlyPrimeTable(); } class PrimeTableTest : public TestWithParamCreatePrimeTableFunc* { public: virtual ~PrimeTableTest() { delete table_; } virtual void SetUp() { table_ (*GetParam())(); } virtual void TearDown() { delete table_; table_ NULL; } protected: PrimeTable* table_; };L53-L79在测试体、夹具构造函数、SetUp()、TearDown()中都可以用GetParam()取到参数本例参数是工厂函数在SetUp()里调用它创建PrimeTable。用TEST_P定义测试L81-L106最后用INSTANTIATE_TEST_CASE_P绑定具体参数值INSTANTIATE_TEST_CASE_P( OnTheFlyAndPreCalculated, PrimeTableTest, Values(CreateOnTheFlyPrimeTable, CreatePreCalculatedPrimeTable1000));L115-L118参数值列表也可以在别的编译单元给出甚至多次实例化。整个文件由#if GTEST_HAS_PARAM_TEST保护在值参数化测试不可用的平台上#else分支里放一个空的 dummy testL120-L128注释解释了原因若条件编译把所有引用gtest_main的代码都裁掉MSVC 链接器不会链接该库会报“入口点未定义”fatal error LNK1561dummy test 能保住gtest_main的链接。Sample #8用 Combine() 生成参数组合sample8_unittest.cc 解决“代码依赖多个全局 flag需要覆盖所有组合”的问题。样例构造了一个HybridPrimeTableL50-L81它内嵌OnTheFlyPrimeTable与PreCalculatedPrimeTable并按输入范围择用且构造函数接受两个 flagforce_on_the_flybool是否禁用预计算表和max_precalculatedint预计算表容量。夹具以二元组::testing::tuplebool, int作为参数类型在SetUp()中解包并构造被测对象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); } virtual void TearDown() { delete table_; table_ NULL; } HybridPrimeTable* table_; };L93-L113注释提到等 C 风格指南允许::std::tr1::tie后可改写为tie(force_on_the_fly, max_precalculated) GetParam()。实例化时用Combine()对每个维度做笛卡尔积INSTANTIATE_TEST_CASE_P(MeaningfulTestParameters, PrimeTableTest, Combine(Bool(), Values(1, 10)));L159-L161参数选取也有讲究见 L153-L158 注释取1小值和10使部分被测数字落在预计算表能力范围外两个有意义的值与Bool()的 true/false 组合共 4 种参数恰好覆盖“预计算表启用/禁用”ד表内/表外”的全部代码路径。同样有#if GTEST_HAS_COMBINE保护与 dummy test 兜底L41、L163-L171。Sample #9Listener API 定制输出 反射 API 检查结果sample9_unittest.cc 演示两件高级事情用 Listener API 替换 Google Test 的控制台输出用 UnitTest 反射 API 枚举用例/测试并检查结果。Listener 部分继承EmptyTestEventListener实现各事件回调得到精简输出器TersePrinterL52-L91OnTestProgramStart(const UnitTest)任何测试活动开始前OnTestStart(const TestInfo)/OnTestEnd(const TestInfo)单个测试开始/结束OnTestPartResult(const TestPartResult)某条断言失败或SUCCEED()触发后可读取file_name()、line_number()、summary()等OnTestProgramEnd(const UnitTest)全部测试结束后用unit_test.Passed()打印总结果。配套的三个测试分别演示普通输出、SUCCEED() ...、以及故意失败EXPECT_EQ(1, 2) ...L93-L104。接线部分在main()L108-L136先InitGoogleTest(argc, argv)解析参数当用户传入--terse_output时取出TestEventListeners通过delete listeners.Release(listeners.default_result_printer());释放并移除默认控制台输出监听器Release会把所有权转移给调用者所以调用者必须 delete再listeners.Append(new TersePrinter)挂上自定义监听器——加入列表后所有权归 Google Test无需手动释放。反射 API 部分RUN_ALL_TESTS()之后通过UnitTest::GetInstance()遍历所有 test case 与 test检查每条测试的通过/失败状态并汇总打印L139 起。这展示了在不重新运行测试的情况下程序化地读取执行结果。Sample #10Listener API 实现简易泄漏检查sample10_unittest.cc 展示了 Listener 的另一个典型用途给测试加一个“不泄漏Water对象”的守卫。实现分三层可追踪的类Water重写了operator new/operator delete维护静态计数器allocated_并提供Water::allocated()查询L51-L72。监听器LeakChecker在OnTestStart记录测试前的存活对象数在OnTestEnd比较差值并断言virtual void OnTestEnd(const TestInfo /* test_info */) { int difference Water::allocated() - initially_allocated_; // You can generate a failure in any event handler except // OnTestPartResult. EXPECT_LE(difference, 0) Leaked difference unit(s) of Water!; }L78-L96注意注释除了OnTestPartResult之外的任意事件处理器里都可以产生断言失败。 3.按需启用main()中解析自定义参数--check_for_leaks启用时才listeners.Append(new LeakChecker)L112-L143。测试DoesNotLeaknew 后 delete应通过LeaksWater只 new 不 delete在开启检查时失败。一个容易踩坑的细节是监听器的顺序语义L131-L137追加到列表末尾的LeakChecker收到OnXyzStart事件时晚于它前面的监听器收到OnXyzEnd事件时早于它们。正因为如此LeakChecker::OnTestEnd()里产生的失败会先被默认文本输出器和 XML 报告器处理、再轮到它们的OnTestEnd()从而被正确归因到当前测试。小结如何把这些样例用于实际开发回到 V1_7_Samples.md 的定位这 10 个文件实际上构成了 Google Test 的核心特性地图#1/#4 覆盖TEST基本用法与断言求值语义#2 覆盖多方法类的组织方式#3/#5 覆盖夹具与夹具继承#6/#7/#8 覆盖接口测试的三大参数化手段#9/#10 覆盖 Listener API 的两个代表性应用。结合 README.md 的构建说明最常用的路径是cmake -Dgtest_build_samplesON ${GTEST_DIR}后编译运行若只需验证最小样例make/目录下的 Makefile 可直接产出sample1_unittest。需要提醒的是本文示例代码基于仓库内置的 gtest 1.7 一代 API如TYPED_TEST_CASE、INSTANTIATE_TEST_CASE_P若升级到更新版本的 gtest这些宏的命名可能有变化请以对应版本的头文件注释为准同时这些宏的可用性受编译器约束样例中GTEST_HAS_TYPED_TEST、GTEST_HAS_PARAM_TEST、GTEST_HAS_COMBINE等条件编译宏正是为跨平台可编译性而设。【免费下载链接】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),仅供参考
返回列表