资讯动态

miniblink49 中的 Google Test v1.7:C++ 单元测试 Primer 完全指南(断言、Fixture 与测试执行机制)

发布时间:2026/9/17 1:45:23 来源:尧图企业网站定制
miniblink49 中的 Google Test v1.7C 单元测试 Primer 完全指南断言、Fixture 与测试执行机制【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49miniblink49 仓库在v8_6_7/testing/下完整收录了 Google TestGoogle C Testing Framework简称 Google Test / GTestv1.7 的源码与文档。本文以官方 Primer入门指南V1_7_Primer.md 为骨架完整继承其中的断言宏体系、Fixture 用法、main()编写规范与已知限制并结合仓库内 gtest v1.7 的真实头文件与实现gtest.h、gtest-internal.h、gtest-port.h等逐一印证其底层机制。读完后你可以直接在 miniblink49 这类 C 项目中搭建一个可编译、可运行、可注册的测试工程并理解TEST()/TEST_F()背后的静态注册原理。为什么需要 Google Test官方对好测试的六条标准Primer 开篇给出了框架设计哲学这六条标准也是理解后文所有 API 的基础独立且可重复independent and repeatable框架让每个测试运行在不同的对象上以相互隔离某个测试失败时可以单独重跑便于调试。组织有序well organized相关测试被归入 test case共享数据与子例程测试结构反映被测代码结构方便跨项目迁移人员维护。可移植且可复用portable and reusable在 Linux、Windows、Mac 上配合 gcc、MSVC 等不同编译器、有无异常支持的配置均可工作。文档同时注明当前发行版的构建脚本只提供 Linux 的 autotools 脚本已废弃其他平台脚本当时仍在开发中——而仓库内的 CMakeLists.txt 正是官方推荐的路径。失败时提供尽可能多的信息Google Test 不会在第一个断言失败处停下整个运行而是中止当前测试、继续下一个测试从而在一次运行-编辑-编译循环中定位多个 bug。免除繁琐工作框架自动跟踪所有已定义的测试无需用户手动枚举。快速可以在多个测试间复用共享资源只支付一次 setup/tear-down 成本同时保持测试相互独立。文档还指出 Google Test 基于经典的 xUnit 架构——用过 JUnit 或 PyUnit 的人会很快上手否则大约 10 分钟即可掌握基础。搭建新的测试工程把 Google Test 编译成库并链接Primer 给出的一步步流程是编译 Google Test 为库。官方为多种构建系统提供工程文件均位于 gtest 根目录本仓库中即 v8_6_7/testing/gtest/msvc/Visual Studio 工程xcode/Mac Xcode 工程make/GNU make 的Makefilecodegear/Borland C Builderautotools 脚本已废弃与CMakeLists.txt推荐。这些目录在仓库中均真实存在msvc/、xcode/、make/、codegear/、CMakeLists.txt。如果你的构建系统不在上述列表内文档建议参考make/Makefile了解编译方式——核心其实只有一条以GTEST_ROOT和GTEST_ROOT/include作为头文件搜索路径编译src/gtest-all.cc。本仓库中该文件位于 src/gtest-all.cc它是一个把全部 gtest 实现汇总到单一编译单元的 amalgamation 文件同目录还有 gtest_main.cc官方main()实现。为测试程序创建项目/构建目标确保头文件搜索路径包含GTEST_ROOT/include这样编译器在编译测试代码时能找到gtest/gtest.h本仓库中为 include/gtest/gtest.h。让测试工程链接 Google Test 库。例如在 Visual Studio 中就是对gtest.vcproj添加一个依赖。如果仍有疑问参考 Google Test 自身测试的构建方式——本仓库 test/ 目录下的gtest_all_test.cc、gtest-message_test.cc等几十个测试文件就是现成范例。基本概念从断言到测试程序的分层结构Primer 定义了四层结构理解它们是读懂所有宏的前提断言assertion检查某条件是否为真的语句。断言结果分为三种success成功、nonfatal failure非致命失败、fatal failure致命失败。发生致命失败时中止当前函数非致命失败则程序照常继续。测试test用断言验证被测代码的行为。若测试崩溃或存在失败断言则测试失败否则成功。测试用例test case包含一个或多个测试。应把测试分组为反映被测代码结构的 test case当多个测试需要共享对象和子例程时放入测试固件类test fixture。测试程序test program可包含多个 test case。从源码结构看这一分层直接映射到头文件组织gtest.h 第 58–66 行通过#include聚合了gtest-death-test.hdeath test、gtest-param-test.h参数化测试、gtest-typed-test.h类型化测试、gtest-test-part.h断言结果单元 TestPartResult等子模块形成 Primer 所说的统一入口gtest/gtest.h。断言Assertions宏、失败消息与 ASSERT 和 EXPECT 的取舍Google Test 的断言是形如函数调用的宏。断言失败时框架打印断言所在的源文件与行号外加失败消息你还能追加自定义消息。ASSERT_* 与 EXPECT_*致命与非致命断言成对出现测试同一件事但效果不同ASSERT_*失败时产生致命失败并立即返回当前函数EXPECT_*失败时产生非致命失败函数继续执行。通常优先使用EXPECT_*因为它允许一个测试中报告多个失败只有当断言失败后继续执行毫无意义时例如后续代码要解引用一个可能为空的指针才用ASSERT_*。文档特别警告了一个微妙问题由于失败的ASSERT_*会立即返回可能跳过后面的清理代码从而造成空间泄漏。因此如果你同时看到断言错误和堆检查器错误要意识到二者可能同源。自定义失败消息用运算符把任意可流式输出到ostream的值串接进宏即可。原文示例ASSERT_EQ(x.size(), y.size()) Vectors x and y are of unequal length; for (int i 0; i x.size(); i) { EXPECT_EQ(x[i], y[i]) Vectors x and y differ at index i; }C 字符串与string对象都支持宽字符串wchar_t*、WindowsUNICODE模式下的TCHAR*、std::wstring在打印时会被转译为 UTF-8。在 gtest.h 中EXPECT_EQ(val1, val2)正是通过内部比较宏展开实现这一行为的失败时按需调用打印操作数。基础布尔断言致命断言非致命断言验证内容ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition 为真ASSERT_FALSE(condition);EXPECT_FALSE(condition);condition 为假无论哪种变体断言失败都意味着其所在的测试失败。二元比较断言致命断言非致命断言验证内容ASSERT_EQ(expected, actual);EXPECT_EQ(expected, actual);expected actualASSERT_NE(val1, val2);EXPECT_NE(val1, val2);val1 ! val2ASSERT_LT(val1, val2);EXPECT_LT(val1, val2);val1 val2ASSERT_LE(val1, val2);EXPECT_LE(val1, val2);val1 val2ASSERT_GT(val1, val2);EXPECT_GT(val1, val2);val1 val2ASSERT_GE(val1, val2);EXPECT_GE(val1, val2);val1 val2使用要点原文逐条保留失败时 Google Test 会同时打印 val1 和 val2在*_EQ*及后续所有相等性断言中把待测表达式放在actual位置、期望值放在expected位置因为失败消息按这一约定做了优化参数必须可被断言的比较运算符比较否则编译报错。自 v1.6.0 起参数不再强制支持流式输出——支持则用它打印不支持则框架尝试以最佳方式打印进一步定制见同目录下的 Google Mock 文档对用户自定义类型定义了对应比较运算符后即可使用定义了对应运算符时优先使用ASSERT_*()宏因为失败时不仅打印比较结果还会打印两个操作数本身参数恰好求值一次带副作用的参数是安全的但与其他 C/C 函数一样求值顺序未定义代码不得依赖特定顺序ASSERT_EQ()对指针做指针相等比较。若用于两个 C 字符串比较的是是否同一内存位置而非内容按值比较 C 字符串如const char*必须用ASSERT_STREQ()断言 C 字符串为NULL用ASSERT_STREQ(NULL, c_string)而比较两个string对象则用ASSERT_EQ本节宏同时支持窄/宽字符串对象string与wstring。C 字符串比较断言本组断言比较两个C 字符串比较两个string对象请改用EXPECT_EQ/EXPECT_NE等。致命断言非致命断言验证内容ASSERT_STREQ(expected_str, actual_str);EXPECT_STREQ(expected_str, actual_str);两个 C 字符串内容相同ASSERT_STRNE(str1, str2);EXPECT_STRNE(str1, str2);两个 C 字符串内容不同ASSERT_STRCASEEQ(expected_str, actual_str);EXPECT_STRCASEEQ(expected_str, actual_str);忽略大小写内容相同ASSERT_STRCASENE(str1, str2);EXPECT_STRCASENE(str1, str2);忽略大小写内容不同注意命名中的 CASE 表示忽略大小写。*STREQ*和*STRNE*还接受宽 C 字符串wchar_t*宽字符串比较失败时其值以 UTF-8 窄字符串形式打印。NULL 指针与空字符串被视为不同。更复杂的字符串技巧子串、前后缀、正则匹配等在 Primer 中指引到同目录的 Advanced Guide本仓库中即 V1_7_AdvancedGuide.md。简单测试TEST() 宏与测试命名规则创建测试三步走用TEST()宏定义并命名一个测试函数普通 C 函数无返回值在函数体内除任意合法 C 语句外用各种断言检查值结果由断言决定任一断言失败无论致命与否或测试崩溃则整个测试失败否则成功。TEST(test_case_name, test_name) { ... test body ... }规则TEST()的参数从一般到具体——第一个参数是 test case 名第二个是测试在该 case 内的名字两者都必须是合法 C 标识符且不能包含下划线_测试全名 test case 名 个体名不同 test case 中的测试可以同名。Primer 中的示例对整数阶乘函数int Factorial(int n);编写测试// Tests factorial of 0. TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(1, Factorial(0)); } // Tests factorial of positive numbers. TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(1, Factorial(1)); EXPECT_EQ(2, Factorial(2)); EXPECT_EQ(6, Factorial(3)); EXPECT_EQ(40320, Factorial(8)); }Google Test 按 test case 分组测试结果因此逻辑相关的测试应使用相同的第一个参数——上例中HandlesZeroInput与HandlesPositiveInput同属FactorialTest。源码印证测试是怎么自动注册的。无需枚举即可运行全部测试并非魔法而是静态初始化。查看 gtest-internal.hGTEST_TEST_宏展开后做了两件事以test_case_name##_##test_name##_Test拼接生成一个继承自父类::testing::Test的测试类测试体放在私有TestBody()中生成一个静态成员test_info_其初始化器调用::testing::internal::MakeAndRegisterTestInfo(...)把 test case 名、测试名、CodeLocation(__FILE__, __LINE__)、SetUpTestCase/TearDownTestCase以及TestFactoryImpl...工厂对象登记进全局测试注册表。也就是说链接进可执行文件的每个TEST()都会在程序启动时通过静态对象构造器自行注册——这正是 Primer框架自动跟踪所有测试的实现基础也解释了后文 Visual C 链接陷阱为何存在。测试固件Test Fixture同一套数据配置服务多个测试当你发现两个以上测试操作相似的数据时就应使用 fixture 复用同一套对象配置。创建步骤Primer 原文五步从::testing::Test派生一个类类体以protected:或public:开头子类需要访问成员在类中声明计划使用的对象如需要编写默认构造函数或SetUp()函数为每个测试准备对象。文档特别提醒一个高频笔误SetUp()别写成小写 u 的Setup()如需要编写析构函数或TearDown()释放SetUp()中分配的资源。何时用构造/析构、何时用SetUp()/TearDown()Primer 指向同目录 FAQV1_7_FAQ.md中的专门条目如需要为测试定义共享子例程。使用 fixture 时改用TEST_F()F 即 fixture以访问固件中的对象与子例程TEST_F(test_case_name, test_name) { ... test body ... }TEST_F()的第一个参数必须是 fixture 类名。两点限制C 宏系统无法让一个宏同时兼容两种测试用错宏会直接编译报错且必须先定义 fixture 类再在TEST_F()中使用否则会收到virtual outside class declaration编译错误。对每个TEST_F()测试Google Test 按以下顺序执行每个测试都拿到全新固件对象运行时新建一个测试固件对象立即调用SetUp()初始化运行测试体调用TearDown()清理删除固件对象。同一 test case 的不同测试拥有不同的固件对象且框架总在下个固件创建之前删除上一个绝不复用一个测试对固件的修改不会影响其他测试。FIFO 队列的完整 Fixture 示例Primer 用Queue类演示。接口template typename E // E is the element type. class Queue { public: Queue(); void Enqueue(const E element); E* Dequeue(); // Returns NULL if the queue is empty. size_t size() const; ... };按惯例fixture 命名为FooTestFoo是被测类名class QueueTest : public ::testing::Test { protected: virtual void SetUp() { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } // virtual void TearDown() {} Queueint q0_; Queueint q1_; Queueint q2_; };本例不需要TearDown()因为析构函数已完成清理。随后用TEST_F()写测试TEST_F(QueueTest, IsEmptyInitially) { EXPECT_EQ(0, q0_.size()); } TEST_F(QueueTest, DequeueWorks) { int* n q0_.Dequeue(); EXPECT_EQ(NULL, n); n q1_.Dequeue(); ASSERT_TRUE(n ! NULL); EXPECT_EQ(1, *n); EXPECT_EQ(0, q1_.size()); delete n; n q2_.Dequeue(); ASSERT_TRUE(n ! NULL); EXPECT_EQ(2, *n); EXPECT_EQ(1, q2_.size()); delete n; }注意ASSERT_TRUE(n ! NULL)的使用动机后续要解引用n若其为NULL将段错误失败后继续执行毫无意义所以用ASSERT_*。经验法则想继续暴露更多错误用EXPECT_*失败后继续执行无意义用ASSERT_*。实际执行时序构造QueueTest对象t1→t1.SetUp()初始化 → 在t1上运行IsEmptyInitially→t1.TearDown()清理 → 析构t1然后对另一个新对象重复上述步骤运行DequeueWorks。另有一个隐藏机制Google Test 在测试对象构造时自动保存所有 Google Test 命令行标志的当前状态并在析构时恢复保证测试间互不干扰。运行测试RUN_ALL_TESTS() 的语义与约束TEST()和TEST_F()会隐式注册测试机制见上节MakeAndRegisterTestInfo因此无需重新列举。定义完成后用RUN_ALL_TESTS()运行全部成功返回0否则返回1。它运行的是当前链接单元中的所有测试——可以来自不同 test case 甚至不同源文件。RUN_ALL_TESTS()被调用时依次保存所有 Google Test 标志状态 → 为第一个测试创建固件对象 →SetUp()初始化 → 在该对象上运行测试 →TearDown()清理 → 删除固件 → 恢复标志状态 → 对下一个测试重复上述步骤直至跑完。边界情况若步骤 2固件构造产生致命失败则步骤 3–5 直接跳过若步骤 3SetUp致命失败则步骤 4 被跳过。两条硬性约束原文 Important 级别不得忽略RUN_ALL_TESTS()的返回值否则gcc会报编译错误。设计动机是自动化测试服务依据退出码而非 stdout/stderr 输出判定测试是否通过因此main()必须return RUN_ALL_TESTS();。只能调用一次。多次调用与部分高级特性如线程安全的 death test冲突不被支持。源码印证在 gtest.h 中该宏定义为int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_;——GTEST_MUST_USE_RESULT_属性正是忽略返回值即编译报错的实现第 2225 行注释亦明确要求在命令行经InitGoogleTest()解析之后才调用它。编写 main()InitGoogleTest 与官方样板Primer 提供的完整样板原样保留可直接复制使用#include this/package/foo.h #include gtest/gtest.h namespace { // The fixture for testing class Foo. class FooTest : public ::testing::Test { protected: // You can remove any or all of the following functions if its body // is empty. FooTest() { // You can do set-up work for each test here. } virtual ~FooTest() { // You can do clean-up work that doesnt throw exceptions here. } // If the constructor and destructor are not enough for setting up // and cleaning up each test, you can define the following methods: virtual void SetUp() { // Code here will be called immediately after the constructor (right // before each test). } virtual void TearDown() { // Code here will be called immediately after each test (right // before the destructor). } // Objects declared here can be used by all tests in the test case for Foo. }; // Tests that the Foo::Bar() method does Abc. TEST_F(FooTest, MethodBarDoesAbc) { const string input_filepath this/package/testdata/myinputfile.dat; const string output_filepath this/package/testdata/myoutputfile.dat; Foo f; EXPECT_EQ(0, f.Bar(input_filepath, output_filepath)); } // Tests that Foo does Xyz. TEST_F(FooTest, DoesXyz) { // Exercises the Xyz feature of Foo. } } // namespace int main(int argc, char **argv) { ::testing::InitGoogleTest(argc, argv); return RUN_ALL_TESTS(); }::testing::InitGoogleTest()解析命令行中的 Google Test 标志并移除已识别的项使用户能通过命令行标志控制测试程序行为这些标志在 gtest.h 中集中声明包括--gtest_filter按 glob 过滤测试、--gtest_list_tests只列出、不运行、--gtest_outputXML 报告、--gtest_break_on_failure、--gtest_color、--gtest_random_seed等详解见 Advanced Guide。必须在RUN_ALL_TESTS()之前调用它否则标志无法正确初始化。在 Windows 上InitGoogleTest()还有宽字符串重载可服务于UNICODE模式编译的程序——对应源码为 gtest.h 中InitGoogleTest(int*, char**)与InitGoogleTest(int*, wchar_t**)两个声明。嫌手写main()麻烦直接把测试链接到gtest_main库即可。本仓库中其实现就是 src/gtest_main.cc。Visual C 用户的重要注意事项这是 Primer 中最长的一段实战警告面向测试放在库里、main()在别的库或 exe 里的 MSVC 用户值得完整继承问题测试将不会被运行。原因是 Visual C 的一个已知链接器缺陷微软反馈库 FeedbackID 244410TEST()宏会创建用于注册测试的静态对象这些对象虽无引用但其构造函数仍应执行而当 VC 链接器发现库中没有任何符号被引用时会把整个库丢弃。解决方案在测试库代码中声明一个导出函数让主程序引用它防止链接器丢库__declspec(dllexport) int PullInMyLibrary() { return 0; }在main程序里调用它通过静态变量的初始化触发int PullInMyLibrary(); static int dummy PullInMyLibrary();若测试定义在静态库非 DLL中则无需__declspec(dllexport)但必须给主程序链接选项加/OPT:NOREFMSVC IDE 中.exe 工程属性 → Configuration Properties → Linker → Optimization将 References 设为Keep Unreferenced Data (/OPT:NOREF)防止链接器丢弃由测试生成的单个符号。还有一个坑如果你把 Google Test 本身用作静态库gtest.vcproj中的默认定义方式你的测试也必须放在静态库中若测试必须放 DLL则必须把 Google Test 也改成构建为 DLL否则测试行为异常甚至完全不运行。Primer 的总结论很直白别把测试写在库里让生命更简单。继续学习与已知限制下一步Primer 指引读者阅读同目录的样例集V1_7_Samples.md与 Advanced Guide。本仓库中 samples/ 目录提供了 sample1–sample10 的完整可编译示例含sample1_unittest.cc、sample10_unittest.cc等test/ 目录则是框架自身的测试集两者都是学习高级特性的最佳参照。已知限制线程安全Google Test 设计上追求线程安全其实现仅在pthreads库可用的系统上是线程安全的在其他系统如 Windows上从两个线程并发使用 Google Test 断言目前是不安全的。多数测试中这不是问题因为断言通常发生在主线程。源码印证gtest-port.h 用GTEST_IS_THREADSAFE宏汇总平台判定——该宏结合GTEST_HAS_PTHREAD在 gtest-port.h 中按 Linux/Mac/HP-UX 等平台启发式定义可用-DGTEST_HAS_PTHREAD0显式关闭等宏计算得出正是 Primer 中有 pthreads 即线程安全这一声明的实现依据。有志者可志愿在gtest-port.h中为自己的平台实现所需的同步原语来贡献支持。小结回到 miniblink49 的工程语境v8_6_7/testing/下的这份 gtest v1.7 是 V8 6.7 测试体系的组成部分其目录布局include/gtest/gtest.h公共 API src/gtest-all.cc单文件实现 gtest_main.cc默认 main CMakeLists.txt/msvc/xcode多构建系统支持恰好是 Primer 所述编译成库再链接流程的实体。掌握本篇内容后你可以按布尔 → 二元比较 → 字符串三组断言宏正确选型EXPECT_*优先、必要时ASSERT_*、C 字符串用STREQ族用TEST()/TEST_F() fixture 组织反映被测结构的测试理解每个测试获得全新固件对象的隔离语义写出规范的main()InitGoogleTest→RUN_ALL_TESTS()且必须使用其返回值或直接链接gtest_main在 MSVC 环境下规避静态注册被链接器丢弃的经典陷阱PullInMyLibrary()技巧与/OPT:NOREF。【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价