ARTICLE DETAIL

资讯详情

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

miniblink49 内置 Google Test V1.5 测试框架指南:断言体系、测试夹具与 RUN_ALL_TESTS 运行机制

miniblink49 内置 Google Test V1.5 测试框架指南:断言体系、测试夹具与 RUN_ALL_TESTS 运行机制 miniblink49 内置 Google Test V1.5 测试框架指南断言体系、测试夹具与 RUN_ALL_TESTS 运行机制【免费下载链接】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 引擎打包的 Google Test 框架文档 V1_5_Primer.md 为主体完整覆盖其六大设计原则、断言宏体系、TEST()/TEST_F()编写方法、测试夹具Fixture模型与RUN_ALL_TESTS()运行机制并结合仓库内v8_6_7/testing/gtest/的真实头文件、源文件与示例工程说明宏定义背后的注册机制与命令行标志实现帮助读者在该浏览器内核代码库中读懂并编写 C 单元测试。一、为什么选择 Google C Testing Framework原文档开宗明义Google C Testing Framework常简称 Google Test的目标是帮助你写出更好的 C 测试且在 Linux、Windows、Mac 上均可工作。文档给出了团队对好的测试的六点共识这也是整个框架的设计纲领独立且可重复independent and repeatable调试一个因其他测试的通过/失败而受影响的测试非常痛苦。Google Test 通过每个测试运行在一个独立的对象上来隔离测试测试失败时还允许将其单独抽出运行以快速调试。良好组织well organized测试应反映被测代码的结构。框架将相关测试分组为测试用例test case用例内的测试可共享数据与子程序这种一致性在多人切换项目接手新代码库时尤其有价值。可移植且可复用portable and reusable开源社区有大量平台无关代码其测试也应是平台无关的。Google Test 在不同操作系统、不同编译器gcc、MSVC 等、开启或关闭异常机制的条件下都能工作。原文档同时注明当时版本仅提供了 Linux 的构建脚本其他平台的脚本仍在开发中——而本仓库的v8_6_7/testing/gtest/目录实际上已带有msvc/Visual Studio、xcode/Mac Xcode、make/GNU make、codegear/Borland C Builder等构建文件印证了文档Setting up a New Test Project一节所列的构建系统清单。失败时提供尽可能多的信息information框架不会在第一次失败处停下来而是仅中止当前测试并继续下一个还可以设置非致命失败后让当前测试继续执行从而在一次运行-编辑-编译循环中发现并修复多个 bug。解放测试作者liberate from housekeeping框架自动跟踪所有已定义的测试无需用户逐一登记即可运行。测试要快fast可以跨测试复用共享资源只为一次性的搭建/拆除付费同时测试之间不互相依赖。由于框架基于流行的 xUnit 架构用过 JUnit 或 PyUnit 的读者会感觉熟悉没用过的人按原文档的说法大约 10 分钟即可上手。二、搭建一个新的测试工程要编写基于 Google Test 的测试程序需要先把 Google Test 编译成库并链接到测试程序。原文档指出的路径是官方为若干流行构建系统提供了构建文件msvc/Visual Studio、xcode/Mac Xcode、make/GNU make、codegear/Borland C Builder以及根目录的 autotools 脚本。在仓库中对应 v8_6_7/testing/gtest/msvc/其中含gtest.sln、gtest.vcproj、gtest_main.vcproj等、v8_6_7/testing/gtest/make/Makefile、v8_6_7/testing/gtest/codegear/ 等目录与文档描述完全一致。如果你的构建系统不在上述清单里参照make/Makefile学习编译方式本质上就是把src/gtest-all.cc编译进来并把GTEST_ROOT与GTEST_ROOT/include加入头文件搜索路径GTEST_ROOT为 Google Test 根目录。仓库中的 v8_6_7/testing/gtest/src/gtest-all.cc 正是这个融合fused后的单文件实现仓库还提供了生成它的脚本 v8_6_7/testing/gtest/scripts/fuse_gtest_files.py。编译出 Google Test 库之后为你的测试程序创建一个工程或构建目标确保头文件搜索路径中包含GTEST_ROOT/include这样编译测试时才能找到gtest/gtest.h仓库中即 v8_6_7/testing/gtest/include/gtest/gtest.h让测试工程链接 Google Test 库——例如在 Visual Studio 中通过对gtest.vcproj添加工程依赖来实现。原文档最后给了一条实用建议仍有疑问时直接看 Google Test 自身测试是如何构建的并把它们当范例——在本仓库中对应的就是 v8_6_7/testing/gtest/test/ 目录下的gtest_unittest.cc等文件。三、基本概念断言、测试、测试用例与测试程序文档对四个核心概念的定义层次是断言assertion检查某个条件是否为真的语句。断言的结果分三种success成功、nonfatal failure非致命失败、fatal failure致命失败。发生致命失败时当前函数被中止abort否则程序正常继续。测试test用断言来验证被测代码的行为。如果测试崩溃或存在失败的断言测试失败否则成功。测试用例test case包含一个或多个测试。应把测试组织成反映被测代码结构的测试用例当多个测试需要共享公共对象和子程序时可放入测试夹具类test fixture class。测试程序test program可包含多个测试用例。文档随后按从单个断言 → 测试 → 测试用例的顺序展开下面几节依此继承。四、断言宏体系ASSERT_* 与 EXPECT_*Google Test 的断言是形似函数调用的宏。测试一个类或函数就是对其行为做断言。断言失败时框架会打印断言所在的源文件名与行号以及失败信息你还可以通过运算符向宏中流式写入自定义失败信息附加在 Google Test 的默认消息之后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; }任何可以流式输出到ostream的东西都可以流式输出到断言宏——特别是 C 字符串和string对象。如果向断言流式输出宽字符串wchar_t*、WindowsUNICODE模式下的TCHAR*、或std::wstring打印时会被转译为 UTF-8。4.1 基本断言致命断言非致命断言验证内容ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition 为真ASSERT_FALSE(condition);EXPECT_FALSE(condition);condition 为假ASSERT_*失败产生致命失败并立即返回当前函数EXPECT_*产生非致命失败函数继续执行。两种情况下断言失败都意味着其所在测试失败。适用平台Linux、Windows、Mac。4.2 二元比较致命断言非致命断言验证内容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 两个操作数。对ASSERT_EQ*/EXPECT_EQ*以及后文所有相等性断言约定把待测表达式放在actual位置、期望值放在expected位置因为失败消息按此约定做了优化。文档还给出了几条重要的使用约束值得逐条记住值参数必须能被断言的比较运算符比较否则编译报错值还必须支持向ostream流式输出的运算符所有内建类型都支持。对自定义类型只有定义了相应比较运算符、等后才能使用且此时优先使用ASSERT_*因为它不仅打印比较结果还会打印两个操作数本身。参数恰好求值一次因此参数带副作用也没问题但和普通 C/C 函数一样参数的求值顺序未定义代码不应依赖任何特定求值顺序。ASSERT_EQ()对指针做的是指针相等判断对两个 C 字符串调用它测试的是它们是否同一内存位置而非内容相同。要按值比较 C 字符串如const char*请使用后文的ASSERT_STREQ()特别地断言一个 C 字符串为NULL应写ASSERT_STREQ(NULL, c_string)。而比较两个string对象则用ASSERT_EQ。本节宏同时支持窄字符串和宽字符串对象string与wstring。4.3 字符串比较这一组断言比较的是两个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);两个 C 字符串内容相同忽略大小写ASSERT_STRCASENE(str1, str2);EXPECT_STRCASENE(str1, str2);两个 C 字符串内容不同忽略大小写注意断言名中的 CASE 表示忽略大小写。*STREQ*与*STRNE*也接受宽 C 字符串wchar_t*两个宽字符串比较失败时其值会以 UTF-8 窄字符串形式打印。另外NULL 指针与空字符串被视为不同。原文档在此处指向进阶文档子串、前缀、后缀、正则匹配等更多字符串技巧见 V1_5_AdvancedGuide.md。关于ASSERT_*与EXPECT_*的总体取舍文档还有一段常被忽略但很关键的告诫失败的ASSERT_*会立即从当前函数返回可能跳过其后的清理代码从而引起空间泄漏这类泄漏视情况可修可不修——如果你在断言错误之外还看到堆检查器heap checker报错要想到这一点。五、简单测试TEST() 宏创建一个测试的三步法用TEST()宏定义并命名一个测试函数——它是普通的、无返回值的 C 函数在函数体内与任意合法 C 语句一起使用各种 Google Test 断言来检查值测试的结果由断言决定若任一断言失败致命或非致命或测试崩溃则整个测试失败否则成功。TEST(test_case_name, test_name) { ... test body ... }TEST()的参数从一般到具体第一个参数是测试用例名第二个参数是测试在用例内的名字。一个测试用例可包含任意多个测试测试的完整名由其所属测试用例名与个体名组成不同测试用例中的测试可以同名。以文档中的阶乘函数为例int Factorial(int n); // Returns the factorial of 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()的第一个参数相同。上例中HandlesZeroInput与HandlesPositiveInput同属用例FactorialTest。源码印证在仓库头文件 v8_6_7/testing/gtest/include/gtest/gtest.h#L2187 中TEST被定义为# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)最终展开到gtest-internal.h中的GTEST_TEST_宏。从 v8_6_7/testing/gtest/include/gtest/internal/gtest-internal.h#L1210-L1235 的宏实现可以看到其隐式注册的原理宏为每个测试生成一个派生自父类的测试类并创建一个指向静态常量的TestInfo*——正是这些静态对象的构造过程完成了测试登记这就是文档所说你不必重新列举所有已定义的测试即可运行它们的底层原因同时也是后文 Visual C 链接坑的根源。仓库自带的示例 v8_6_7/testing/gtest/samples/sample4_unittest.cc 则给出了一个最简的TEST(Counter, Increment)用法并在注释中呼应了断言参数恰好求值一次、可以带副作用的语义。更多示例集中在 v8_6_7/testing/gtest/samples/ 目录sample1.cc~sample10_unittest.cc文档 Samples.md 有对应的文字说明。六、测试夹具让多个测试复用同一份数据配置当你发现两个或多个测试在操作相似数据时可以使用测试夹具test fixture让若干测试复用同一组对象的配置。文档给出的创建夹具五步法派生一个类自::testing::Test。类体以protected:或public:开头因为子类要访问夹具成员在类中声明计划使用的对象如有必要写默认构造函数或SetUp()函数为每个测试准备对象。常见拼写错误是把SetUp()写成小写 u 的Setup()务必避免如有必要写析构函数或TearDown()函数释放SetUp()中分配的资源。何时用构造/析构函数、何时用SetUp()/TearDown()文档指向了 FAQ 条目V1_5_FAQ.md如有需要为测试定义共享的子程序。使用夹具时改用TEST_F()而不是TEST()它允许你访问夹具中的对象和子程序TEST_F(test_case_name, test_name) { ... test body ... }与TEST()类似第一个参数是测试用例名但对TEST_F()而言它必须是测试夹具类的名字后缀_F即 fixture 之意。文档同时警告两点C 宏系统无法造出一个同时处理两种测试的宏用错宏会直接编译报错且必须先定义夹具类再在TEST_F()中使用它否则会出现 virtual outside class declaration 编译错误。对于每个用TEST_F()定义的测试Google Test 会在运行时创建一个全新的测试夹具对象立即调用SetUp()初始化它在该对象上运行测试调用TearDown()清理删除夹具。同一用例的不同测试拥有不同的夹具对象Google Test 总是先删除一个夹具再创建下一个从不复用同一夹具某个测试对夹具的改动不会影响其他测试。文档以 FIFO 队列类Queue为例接口Enqueue(const E)、Dequeue()空队列返回 NULL、size()等先按约定被测类为Foo则夹具命名为FooTest定义夹具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_*与EXPECT_*文档总结的取舍法则很有实战价值希望断言失败后继续暴露更多错误时用EXPECT_*失败后继续执行没有意义时用ASSERT_*。例如DequeueWorks中的ASSERT_TRUE(n ! NULL)——因为后面要解引用指针n若n为NULL会段错误。运行时实际发生的顺序是构造QueueTest对象t1→t1.SetUp()初始化 → 在t1上运行IsEmptyInitially→t1.TearDown()清理 →t1析构随后在另一个QueueTest对象上重复以上步骤运行DequeueWorks。源码印证TEST_F的定义见 v8_6_7/testing/gtest/include/gtest/gtest.h#L2216-L2218TEST_F(test_fixture, test_name)展开为GTEST_TEST_(test_fixture, test_name, test_fixture, ::testing::internal::GetTypeIdtest_fixture())可见其把夹具类本身作为父类传入并用GetTypeId记录类型——这正是夹具类名必须与测试用例名一致的机制性原因。文档最后还注明Google Test 在测试对象构造时自动保存所有 Google Test 标志位状态并在其析构时恢复。这一行为可以在 v8_6_7/testing/gtest/src/gtest.cc#L2215 中得到印证——测试对象构造时即创建GTEST_FLAG_SAVER_对象标志的保存/恢复由 RAII 自动完成。七、运行测试RUN_ALL_TESTS()TEST()和TEST_F()会隐式地把测试注册到 Google Test。因此与许多其他 C 测试框架不同你不需要重新罗列所有已定义的测试就能运行它们。定义完测试后用RUN_ALL_TESTS()运行——全部成功返回0否则返回1。注意RUN_ALL_TESTS()运行的是你的链接单元内的全部测试——它们可以来自不同测试用例甚至不同源文件。被调用时RUN_ALL_TESTS()依次保存所有 Google Test 标志的状态 → 为第一个测试创建夹具对象 → 用SetUp()初始化 → 在夹具对象上运行测试 → 用TearDown()清理 → 删除夹具 → 恢复所有标志状态 → 对下一个测试重复以上步骤直到全部跑完。此外如果第 2 步夹具构造函数产生致命失败第 35 步将跳过如果第 3 步SetUp()产生致命失败第 4 步跳过。两条强约束原文档以 Important 标注不得忽略RUN_ALL_TESTS()的返回值否则gcc会报编译错误。原因是自动化测试服务依据退出码而非 stdout/stderr 判断测试是否通过所以你的main()必须返回RUN_ALL_TESTS()的值。仓库源码中这一点被显式固化v8_6_7/testing/gtest/include/gtest/gtest.h#L2230-L2234 声明int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_;GTEST_MUST_USE_RESULT_宏在忽略返回值时触发编译器告警实现体就是return ::testing::UnitTest::GetInstance()-Run();。只应调用一次RUN_ALL_TESTS()。多次调用与某些高级特性如线程安全死亡测试冲突因此不受支持。命令行标志main()中解析出的标志--gtest_filter、--gtest_repeat、--gtest_shuffle、--gtest_break_on_failure、--gtest_throw_on_failure、--gtest_list_tests等在 v8_6_7/testing/gtest/src/gtest.cc 中实现——UnitTest::Run()会读取GTEST_FLAG(repeat)、GTEST_FLAG(shuffle)、GTEST_FLAG(break_on_failure)、GTEST_FLAG(throw_on_failure)等标志决定重跑、乱序与失败中断行为标志值还可由环境变量提供。完整的标志语义文档在 V1_5_AdvancedGuide.md。八、编写 main() 函数原文档给出了一份可直接套用的样板代码#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 标志并移除已识别的标志让用户通过标志控制测试程序行为详见 V1_5_AdvancedGuide.md。必须在RUN_ALL_TESTS()之前调用它否则标志不会被正确初始化。在 Windows 上InitGoogleTest()也支持宽字符串可用于UNICODE模式编译的程序。如果嫌手写main()麻烦Google Test 提供了main()的基础实现直接把测试链接到gtest_main库即可。仓库中的 v8_6_7/testing/gtest/src/gtest_main.cc 正是这份基础实现全文核心仅三行GTEST_API_ int main(int argc, char **argv) { printf(Running main() from gtest_main.cc\n); testing::InitGoogleTest(argc, argv); return RUN_ALL_TESTS(); }Visual C 用户的特别注意原文档用单独小节强调了一个 Windows 平台的深坑如果把测试放进一个库而main()在另一个库或 .exe 中这些测试将不会运行。原因是 Visual C 的链接器行为缺陷定义测试时 Google Test 会创建用于注册的静态对象它们虽不被其他地方引用但构造函数仍应执行而 MSVC 链接器发现库中无符号被外部引用时会把整个库剔除。解决办法分三步在库中声明一个导出函数__declspec(dllexport) int PullInMyLibrary() { return 0; }若测试放在静态库而非 DLL 中则不需要__declspec(dllexport)。 2. 在主程序中调用它以保留引用int PullInMyLibrary(); static int dummy PullInMyLibrary();这使测试保持被引用状态从而在启动时自注册。 3. 若测试定义在静态库中还需给主程序链接选项加/OPT:NOREF使用 MSVC IDE 时在 .exe 工程属性/Configuration Properties/Linker/Optimization 中将 References 设置为Keep Unreferenced Data (/OPT:NOREF)防止链接器丢弃测试生成的个别符号。最后一个陷阱如果 Google Test 以静态库方式使用gtest.vcproj中即如此定义你的测试也必须位于静态库中若测试必须在 DLL 中则必须同时把 Google Test 改为构建 DLL否则测试不能正确运行甚至完全不会运行。文档给出的总体结论是别给自己添堵——不要把测试写进库里。九、已知限制与线程安全原文档Known Limitations一节说明Google Test 在设计上是线程安全的其实现只在拥有pthreads库的系统上真正线程安全在其他系统如当时的 Windows上从两个线程并发使用断言目前是不安全的。多数测试中这不是问题因为断言通常在主线程执行。若你想贡献可以自愿在gtest-port.h中实现你所在平台所需的同步原语——仓库中对应文件为 v8_6_7/testing/gtest/src/gtest-port.cc 及其头文件所在的include/gtest/internal/目录平台移植层GTEST_PORT正是各平台线程、文件、字符串适配的集中点。十、后续学习路径完成以上基础后按原文档Where to Go from Here的指引开始编写并运行 Google Test 测试阅读示例代码 Samples.md对应源码在 v8_6_7/testing/gtest/samples/继续学习 V1_5_AdvancedGuide.md其中涵盖死亡测试、参数化测试、类型化测试、更多命令行标志等进阶特性遇到环境搭建类疑问时查阅 V1_5_FAQ.md。适用前提与限制说明本文所有路径与源码证据均来自当前仓库内v8_6_7/testing/gtest/目录即 miniblink49 随 V8 6.7 引擎版本打包的 Google Test V1.5 文档与配套源码该目录还并存 V1_6、V1_7 等更新版本的 Primer 文档如 V1_7_Primer.md若你在本仓库中面向其他 V8 目录编写测试应以对应版本的文档为准。仓库中的 gtest 是 V8/引擎构建体系的一部分主要服务于组件自身的单元测试若在外部工程使用需按本文第二节自行编译src/gtest-all.cc并配置头文件搜索路径与链接依赖。【免费下载链接】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),仅供参考
返回列表