ARTICLE DETAIL

资讯详情

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

miniblink49 内置 Google Test V1.5 FAQ:C++ 断言、死亡测试与 Fixture 的设计抉择及排错手册

miniblink49 内置 Google Test V1.5 FAQ:C++ 断言、死亡测试与 Fixture 的设计抉择及排错手册 miniblink49 内置 Google Test V1.5 FAQC 断言、死亡测试与 Fixture 的设计抉择及排错手册【免费下载链接】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 5.7 测试树内置的 Google TestGTestV1.5 版官方 FAQV1_5_FAQ.md展开逐条解析其关于断言宏设计取舍、死亡测试death test实现原理、Fixture 继承与私有成员测试等核心问题的官方答复并结合仓库内 GTest 的源码如 gtest-death-test.cc、MSVC 工程文件msvc/gtest.sln与示例samples/sample5_unittest.cc进行佐证。读完本文你将掌握 V8 测试树中 C 单元测试的完整设计约束能独立排查EXPECT_*/ASSERT_*、ASSERT_DEATH、RUN_ALL_TESTS()等常见编译与运行期错误。一、文档在仓库中的位置与适用前提GTest 在 miniblink49 中以 V8 子树的形式存在每个 V8 版本目录v8_4_5/、v8_4_8/、v8_5_1/、v8_5_7/、v8_6_7/、v8_7_5/都带有独立的测试目录。本文依据的是v8_5_7/testing/gtest/docs/V1_5_FAQ.md其配套文档为 V1_5_Primer.md入门指南与 V1_5_AdvancedGuide.md高级指南。该目录同时保留了 V1.5 至 V1.7 多个版本的 Primer、AdvancedGuide、FAQ、PumpManual 与 XcodeGuide从文档结构看这是 V8 5.7 快照时期 GTest 的历史版本归档本文结论仅适用于 V1.5 这一版本的行为与语法。GTest 在本目录下的完整组成包括源码与头文件src/、include/gtest/gtest.h自测用例test/gtest-death-test_test.cc 等示例samples/ 下的 sample1 至 sample10构建脚本CMakeLists.txt、Makefile.am、msvc/ 下的 Visual Studio 2005 工程。文档开头声明如果答案未能在 FAQ 中找到且已阅读 Primer 与 AdvancedGuide应将其提交给 GTest 官方邮件列表googletestframework 邮件组。二、为什么选择 Google Test官方列出的特性组合FAQ 首先开宗明义GTest 团队无意宣称某个框架是最好的C 测试框架选工具要看具体任务。他们创建 GTest 是因为在现有框架中找不到满足自身需求的功能与便利组合。以下是官方列出的、他们喜欢 GTest 的理由FAQ 强调这些特性并非 GTest 独有而是其组合构成了选择依据可移植性GTest 设计为可移植在多数 STL 类型如std::string、std::vector无法编译的环境中也能工作不要求异常或 RTTI因此可以在 Linux、Mac OS X、Windows 及若干嵌入式操作系统上运行。非致命断言省时EXPECT_*系列宏允许单次编辑—编译—测试循环中报告多处失败被证明是巨大的时间节约者。流式信息附加用流语法即可为断言生成信息丰富的消息例如ASSERT_EQ(5, Foo(i)) where i i;不需要一套新的宏或特殊函数。自动发现测试GTest 自动检测测试无需手工枚举即可运行。可扩展的断言词汇通过EXPECT_PRED*方便地扩展断言也可以基于EXPECT_PRED*以极简方式定义自己的断言宏。死亡测试实用death test 便于确保生产代码中的断言由正确的条件触发。SCOPED_TRACE当断言失败来自子程序或循环内部时帮助你理解失败上下文。按名称模式筛选测试可用名称模式决定运行哪些测试便于快速复现失败。三、平台与构建问题3.1 在 Visual Studio 2008 上生成 64 位二进制FAQ答者Trevor Robinson给出的完整流程加载随附的 Visual Studio 解决方案文件即msvc/gtest-md.sln或msvc/gtest.sln本仓库中对应 msvc/gtest-md.sln 与 msvc/gtest.sln。通过迁移向导将解决方案与工程文件迁移到 Visual Studio 2008。在Build菜单选择Configuration Manager...在Active solution platform下拉框选New...在新平台下拉框选x64Copy settings from保持Win32勾选Create new project platforms点击OK。此后Win32与x64两种平台配置出现在Standard工具栏可切换构建 32 位或 64 位二进制用 Batch Build 可同时构建。为防止两个平台的输出文件互相覆盖需要修改新平台配置的Intermediate Directory在Solution Explorer中多选shift-click所有工程不要选中解决方案本身右键选择Properties左侧选Configuration PropertiesConfiguration下拉框选All Configurations确认当前平台为x64将Intermediate Directory从$(PlatformName)\$(ConfigurationName)改为$(OutDir)\$(ProjectName)。点击OK并构建64 位二进制将输出到msvc\x64\Debug目录。3.2 MinGW 支持现状官方未亲自测试 MinGW但有用户报告从 Cygwin 使用 MinGW 时能成功编译安装 GTest需要如下配置PATH/TO/configure CCgcc -mno-cygwin CXXg -mno-cygwin理论上可以用直接链接真实 MinGW 二进制的选项替换-mno-cygwin但官方未尝试过。注意事项Caveats编译时会产生大量警告make check会报部分错误因为 GTest 自身的部分测试与 MinGW 不兼容。另有报告称按照 WxWidgets 维基上的 Linux 交叉编译指引可以在 Linux 上成功交叉编译 GTest 的 MinGW 二进制。如果对改善 MinGW 支持有兴趣可联系 GTest 邮件组。四、断言宏的设计取舍4.1 为什么支持EXPECT_EQ(NULL, ptr)而不支持EXPECT_NE(NULL, ptr)由于 C 的某些怪癖要在EXPECT_XX()与ASSERT_XX()宏中接受NULL参数需要一些非平凡的模板元编程技巧因此官方只在最需要的地方实现否则会使 GTest 实现更难维护、更易出错EXPECT_EQ()的约定是第一个参数为期望值第二个参数为实际值。有人合理地想写EXPECT_EQ(NULL, some_expression)且被多次提出因此官方实现了它。EXPECT_NE(NULL, ptr)的需求远没那么强断言失败时你已经知道ptr一定是NULL此时打印 ptr 不增加任何信息量用EXPECT_TRUE(ptr ! NULL)效果等同。若支持EXPECT_NE(NULL, ptr)出于一致性还必须支持EXPECT_NE(ptr, NULL)——与EXPECT_EQ不同EXPECT_NE对两个参数的顺序没有约定。这意味着实现中要用两次模板元编程技巧理解与维护成本更高收益不足以抵偿成本。最后随着 GMock matcher 库的发展官方鼓励测试中更多地使用统一的EXPECT_THAT(value, matcher)语法。matcher 方案的重要优势是 matcher 易于组合成新的 matcher而EXPECT_NE等宏难以组合。因此官方更愿意投资 matcher 而非EXPECT_XX()宏。4.2 为什么断言不用异常实现最初动机是能在使用者禁用了异常的项目中使用 GTest。之后又意识到额外好处在析构函数中抛出异常在 C 中是未定义行为。不用异常意味着 GTest 的断言可以安全地用在析构函数中。EXPECT_*系列宏在失败后会继续执行允许一次运行中报告一个TEST内的多个失败。这是广受欢迎的特性因为 C 的编辑—编译—测试循环通常较长一次能修多个问题是很好的事。若断言用异常实现测试可能被用户代码错误地吞掉失败try { ... ASSERT_TRUE(...) ... } catch (...) { ... }上述代码即使ASSERT_TRUE抛出了异常也会通过测试。虽然在测试里直接这么写不太可能但当你在被测试代码调用的回调中写断言时可能撞见这种模式。不用异常的缺点是ASSERT_*用return实现只会中止当前函数而不是当前TEST。4.3 为什么 fixture 版与普通版要用两个宏TEST与TEST_FC 宏系统不允许同一宏同时覆盖两种情形。一种替代方案是只提供一个带 fixture 的宏要求用户有时定义空 fixtureclass FooTest : public ::testing::Test {}; TEST_F(FooTest, DoesThis) { ... }或者typedef ::testing::Test FooTest; TEST_F(FooTest, DoesThat) { ... }但许多人认为这多了一行。官方目标是让写测试尽可能容易因此让简单测试的创建变得极简这意味着为这类测试使用单独的宏。官方承认两种方案都不是理想方案但各自都合理最终差别其实不大。4.4 为什么不用 struct 作为测试 fixture官方只在表示被动数据passive data时才用 struct。struct 与 class 的这种区分有利于记录代码作者意图。测试 fixture 带有SetUp()与TearDown()之类的逻辑因此更适合定义为 class。五、死亡测试Death Test深入解析5.1 为什么死亡测试实现为断言而不是测试运行器目标是让死亡测试对用户尽可能方便。具体来说runner 式方案要求把信息拆成两半死亡测试本身的定义用 C 写以及 runner 关于如何运行、期望什么的规范可能用别的语言写。用户需要小心保持两者同步。ASSERT_DEATH(statement, expected_message)在同一个地方、同一种语言中给出全部必要信息无样板代码非常声明式。ASSERT_DEATH的语法与错误报告语义同其他 GTest 断言一致易学。ASSERT_DEATH可与其他断言和逻辑随意混用不受一个测试方法一个死亡测试的限制例如if (FooCondition()) { ASSERT_DEATH(Bar(), blah); } else { ASSERT_EQ(5, Bar()); }若偏好一个方法一个死亡测试也可以但官方不愿强加这种限制——人为限制越少越好。ASSERT_DEATH可以引用当前函数的局部变量并可基于运行时信息决定要写多少个死亡测试例如const int count GetCount(); // Only known at run time. for (int i 1; i count; i) { ASSERT_DEATH({ double* buffer new double[i]; ... initializes buffer ... Foo(buffer, i) }, blah blah); }runner 式方案倾向于更静态、更不灵活或需要更多用户努力才能获得这种灵活性。5.2 fork() 实现的快速性原理FAQ 指出ASSERT_DEATH调用fork()创建子进程运行死亡测试这极快——因为fork()使用写时复制copy-on-write页开销几乎为零且子进程直接从用户给定的语句开始执行跳过所有全局/局部初始化以及到达该语句之前的任何代码。如果从零启动子进程当测试动态链接大量库时仅加载与启动就可能需要数秒。这一点在仓库源码中得到印证gtest-death-test.cc 中const pid_t child_pid fork();即死亡测试子进程的创建点同一文件 L1100 附近 还包含use_fork (child_pid fork()) 0的分支表明 POSIX 平台始终优先使用fork()路径。5.3 死亡测试中的状态修改为何丢失死亡测试EXPECT_DEATH等在子进程中执行以便预期的崩溃不会杀死测试程序即父进程。因此它们在内存中产生的任何副作用只在各自的子进程中可观察父进程中不可观察。可以理解为它们在平行宇宙中运行。5.4 死亡测试挂起或段错误如何修复在 GTest 中死亡测试在子进程中运行工作机制很精巧写死亡测试前必须真正理解其工作原理。重点排查方向死亡测试不欢迎父进程中存在多个线程。首先尝试消除在EXPECT_DEATH()之外创建线程。有时这不可能——某些必须使用的库可能在main()之前就创建了线程。此时可以把尽可能多的活动移到EXPECT_DEATH()内部极端情况下把一切都移进去或者在其中保留尽可能少的事情另外可以尝试把死亡测试风格设为threadsafe更安全但更慢看是否有帮助。仓库源码 gtest-death-test.cc L90 附近 中--gtest_death_test_style的说明即为threadsafe (child process re-executes the test binary ...)与 L1198-L1215 对threadsafe/fast两种风格的分支解析相吻合。若选择线程安全的死亡测试请记住子进程会从程序开头重新运行测试程序。因此确保你的程序能与自身并行运行且是确定性的。归根结底这是良好的并发编程必须确保程序中没有竞态条件或死锁。没有银弹。5.5 pthread 的manager 线程问题在 Linux pthread 库下一旦跨过从单线程到多线程的门槛就没有回头路第一次创建线程时系统还会额外创建一个 manager 线程所以你得到 3 个而不是 2 个线程。之后你创建的线程与主线程 join 时线程数减 1但 manager 线程永远不会被杀死因此仍剩 2 个线程意味着不能安全运行死亡测试。新的 NPTL 线程库没有这个问题不创建 manager 线程但如果你无法控制测试运行的机器就不应依赖这一点——这就是ASSERT_DEATH抱怨已经 join 的先前线程的原因。5.6 为什么含ASSERT_DEATH时要求整个测试用例命名为FOODeathTestGTest 不会交错运行不同测试用例中的测试先完整运行一个用例的所有测试再运行下一个。这样是因为用例在第一个测试运行前需要设置、结束后需要拆除拆散用例意味着多次设置/拆除效率低且语义不干净。如果改为按测试名而非用例名决定顺序会遇到这样的矛盾TEST_F(FooTest, AbcDeathTest) { ... } TEST_F(FooTest, Uvw) { ... } TEST_F(BarTest, DefDeathTest) { ... } TEST_F(BarTest, Xyz) { ... }由于FooTest.AbcDeathTest需要在BarTest.Xyz之前运行而又不交错运行不同用例必须在FooTest中所有测试之前完成才能开始BarTest中任何测试这与必须在FooTest.Uvw之前运行BarTest.DefDeathTest的要求直接矛盾。替代做法如果不想把整个用例命名为FOODeathTest其中既有死亡测试又有普通测试可以把用例拆成FooTest与FooDeathTest用命名表明两者的关联class FooTest : public ::testing::Test { ... }; TEST_F(FooTest, Abc) { ... } TEST_F(FooTest, Def) { ... } typedef FooTest FooDeathTest; TEST_F(FooDeathTest, Uvw) { ... EXPECT_DEATH(...) ... } TEST_F(FooDeathTest, Xyz) { ... ASSERT_DEATH(...) ... }5.7ASSERT_DEATH的 statement 参数可以是什么ASSERT_DEATH(_statement_, _regex_)或任何死亡断言宏可以在_statement_合法的任何地方使用因此_statement_基本可以是当前上下文中任何有意义的 C 语句。它可以引用全局和/或局部变量可以是简单函数调用常见情况、复杂表达式或复合语句。官方给出的完整示例// A death test can be a simple function call. TEST(MyDeathTest, FunctionCall) { ASSERT_DEATH(Xyz(5), Xyz failed); } // Or a complex expression that references variables and functions. TEST(MyDeathTest, ComplexExpression) { const bool c Condition(); ASSERT_DEATH((c ? Func1(0) : object2.Method(test)), (Func1|Method) failed); } // Death assertions can be used any where in a function. In // particular, they can be inside a loop. TEST(MyDeathTest, InsideLoop) { // Verifies that Foo(0), Foo(1), ..., and Foo(4) all die. for (int i 0; i 5; i) { EXPECT_DEATH_M(Foo(i), Foo has \\d errors, ::testing::Message() where i is i); } } // A death assertion can contain a compound statement. TEST(MyDeathTest, CompoundStatement) { // Verifies that at lease one of Bar(0), Bar(1), ..., and // Bar(4) dies. ASSERT_DEATH({ for (int i 0; i 5; i) { Bar(i); } }, Bar has \\d errors);}FAQ 还提到 GTest 自身的单元测试文件中有更多示例在本仓库中对应的自测文件为 test/gtest-death-test_test.cc。5.8 死亡测试正则表达式语法在 POSIX 系统上GTest 使用 POSIX 扩展正则表达式POSIX Extended Regular Expression语法在 Windows 上使用一个受限的正则表达式语法变体。更多细节见 V1_5_AdvancedGuide.md 中的正则表达式语法章节。六、测试用例与 Fixture 的编写问题6.1 编译器报undefined references静态 const 成员变量如果类中有静态数据成员// foo.h class Foo { ... static const int kBar 100; };还需要在foo.cc中类体外部定义它const int Foo::kBar; // No initializer here.否则代码是无效的 C可能以意想不到的方式出错。特别地把它用在 GTest 的比较断言EXPECT_EQ等中会产生 undefined reference 链接错误。6.2 能否从一个 fixture 派生另一个 fixture可以。每个测试 fixture 对应一个同名的测试用例这意味着一个特定 fixture 只能被一个用例使用。但有时多个用例想使用相同或略有不同的 fixture——例如想确保某个 GUI 库的所有测试用例都不泄漏字体、画刷等重要的系统资源。在 GTest 中把共享逻辑放进基类 fixture再为每个想使用这些公共逻辑的用例从基类派生一个 fixture然后用TEST_F()编写各派生 fixture 的测试。典型代码// Defines a base test fixture. class BaseTest : public ::testing::Test { protected: ... }; // Derives a fixture FooTest from BaseTest. class FooTest : public BaseTest { protected: virtual void SetUp() { BaseTest::SetUp(); // Sets up the base fixture first. ... additional set-up work ... } virtual void TearDown() { ... clean-up work for FooTest ... BaseTest::TearDown(); // Remember to tear down the base fixture // after cleaning up FooTest! } ... functions and variables for FooTest ... }; // Tests that use the fixture FooTest. TEST_F(FooTest, Bar) { ... } TEST_F(FooTest, Baz) { ... } ... additional fixtures derived from BaseTest ...必要时可以从派生 fixture 继续派生GTest 对层级深度没有限制。使用派生 fixture 的完整示例见 samples/sample5_unittest.cc。多个用例共享 fixture 逻辑的简化写法不必为每个用例都定义新的 fixture 类。与其写class FooTest : public BaseTest {}; TEST_F(FooTest, Abc) { ... } TEST_F(FooTest, Def) { ... } class BarTest : public BaseTest {}; TEST_F(BarTest, Abc) { ... } TEST_F(BarTest, Def) { ... }不如直接typedeffixturetypedef BaseTest FooTest; TEST_F(FooTest, Abc) { ... } TEST_F(FooTest, Def) { ... } typedef BaseTest BarTest; TEST_F(BarTest, Abc) { ... } TEST_F(BarTest, Def) { ... }6.3 接口有多个实现时能否写一遍测试重复跑FAQV1.5 时期的答复GTest 当时对这类测试以及一般的数据驱动测试支持还不完善官方希望近期改善。结合本仓库文档演进看后续版本指南如 V1.6/V1.7逐步补齐了参数化测试支持而 V1.5 FAQ 末尾推荐的value-parameterized tests值参数化测试见 V1_5_AdvancedGuide.md 对应章节即是该版本可用的替代手段让你用不同参数重复同一个测试而无需定义多次。6.4 编译器抱怨void value not ignored as it ought to be你大概率是在一个不返回void的函数里使用了ASSERT_*()。ASSERT_*()只能在void函数中使用因为它通过return中止当前函数。6.5 该用 fixture 的构造/析构函数还是 SetUp/TearDown首先记住GTest 不会跨多个测试复用同一个 fixture 对象。对每个TEST_FGTest 都会创建一个全新的 fixture 对象立即调用SetUp()运行测试调用TearDown()然后立即删除 fixture 对象。因此如果构造函数或析构函数已经完成了工作就没有必要写SetUp()或TearDown()。但仍应在以下情况使用SetUp()/TearDown()如果拆除操作可能抛出异常必须用TearDown()而非析构函数——在析构函数中抛出是未定义行为通常会直接杀死程序。注意许多标准库如 STL在编译器启用异常时可能抛出。因此若要写可移植的测试无论是否启用异常都能工作应优先TearDown()。GTest 团队正在考虑在启用异常的平台上让断言宏抛出异常如 Windows、Mac OS、Linux 客户端侧这将免除用户把失败从子程序传播给调用者的负担。因此若你的代码可能运行在这些平台上不应在析构函数中使用 GTest 断言。在构造或析构函数中不能对该对象做虚函数调用可以调用声明为 virtual 的方法但会静态绑定。因此若需要调用一个将被派生类重写的方法必须使用SetUp()/TearDown()。SetUp 没被调用C 对大小写敏感正确拼写是SetUp()——你是不是写成了Setup()同理有人把SetUpTestCase()拼成SetupTestCase()然后纳闷它为何从不调用。6.6 为什么优先使用 fixture 而非全局变量测试很可能要修改全局变量状态这使副作用很难避免从一个测试逃逸并污染其他测试调试困难。使用 fixture 时每个测试拥有一组全新名字相同但彼此独立的变量测试相互保持独立。全局变量污染全局命名空间。fixture 可以通过子类化复用全局变量难以做到这一点这在许多用例有公共内容时很有用。6.7 编译器抱怨 fixture 类no matching function for call to Foo::Foo()GTest 必须能创建你的 fixture 类对象因此它必须有默认构造函数。通常编译器会自动生成但以下情况必须自己定义如果你显式声明了Foo的非默认构造函数就需要定义一个默认构造函数哪怕它是空的。如果Foo有 const 非静态数据成员你必须定义默认构造函数并且在构造函数的初始化列表中初始化该 const 成员。早期版本的 gcc 不强制初始化 const 成员这是已在 gcc 4 修复的 bug。七、私有成员与 main() 的测试策略7.1 不写FRIEND_TEST()测试私有成员应当尝试写可测试的代码——类应当能从其公共接口被方便地测试。一种途径是 Pimpl 惯用法把类的所有私有成员移到一个辅助类中并把辅助类的所有成员设为 public。此外还有几个不需要FRIEND_TEST的选项把测试写成 fixture 类的成员函数class Foo { friend class FooTest; ... }; class FooTest : public ::testing::Test { protected: ... void Test1() {...} // This accesses private members of class Foo. void Test2() {...} // So does this one. }; TEST_F(FooTest, Test1) { Test1(); } TEST_F(FooTest, Test2) { Test2(); }在 fixture 类中为被测类的私有成员写访问器然后在测试中使用访问器class Foo { friend class FooTest; ... }; class FooTest : public ::testing::Test { protected: ... T1 get_private_member1(Foo* obj) { return obj-private_member1_; } }; TEST_F(FooTest, Test1) { ... get_private_member1(x) ... }如果方法是protected的可以在一个仅用于测试的子类中改变其访问级别class YourClass { ... protected: // protected access for testability. int DoSomethingReturningInt(); ... }; // in the your_class_test.cc file: class TestableYourClass : public YourClass { ... public: using YourClass::DoSomethingReturningInt; // changes access rights ... }; TEST_F(YourClassTest, DoSomethingTest) { TestableYourClass obj; assertEquals(expected_value, obj.DoSomethingReturningInt()); }7.2 不写FRIEND_TEST()测试私有静态成员FAQ 认为私有静态方法让头文件显得杂乱——它们是实现细节理想情况下不应出现在 .h 中所以经常把它们做成自由函数。与其写// foo.h class Foo { ... private: static bool Func(int n); }; // foo.cc bool Foo::Func(int n) { ... } // foo_test.cc EXPECT_TRUE(Foo::Func(12345));最好写成// foo.h class Foo { ... }; // foo.cc namespace internal { bool Func(int n) { ... } } // foo_test.cc namespace internal { bool Func(int n); } EXPECT_TRUE(internal::Func(12345));7.3 测试定义了main()的文件要测试foo.cc需要把它编译链接进单元测试程序。但若该文件包含main()的定义会与单元测试的main()冲突产生构建错误。正确的解法是拆成三个文件foo.h包含声明foo.cc包含除main()外的定义foo_main.cc只包含main()的定义。这样foo.cc就能被方便地测试。如果是在现有文件上追加测试、不想做这种侵入式改动有一个 hack把整个foo.cc包含进单元测试里。例如// File foo_unittest.cc // The headers section ... // Renames main() in foo.cc to make room for the unit test main() #define main FooMain #include a/b/foo.cc // The tests start here. ...请记住这只是 hack只应作为最后手段。7.4 断言中自定义类型报no match for operator如果断言中使用了自定义类型FooType必须确保定义了std::ostream operator(std::ostream, const FooType)函数以便能够打印FooType的值。此外如果FooType声明在某个命名空间中操作符也必须定义在同一个命名空间中。八、谓词断言与RUN_ALL_TESTS()的常见陷阱8.1ASSERT_PREDn报 no matching function to call如果ASSERT_PRED*或EXPECT_PRED*中使用的谓词函数是重载的或模板编译器难以判断应选哪个重载版本。ASSERT_PRED_FORMAT*与EXPECT_PRED_FORMAT*没有这个问题。见到该错误时可切换到(ASSERT|EXPECT)_PRED_FORMAT*失败信息也会更好。如果不可行可以显式告诉编译器选哪个版本。例如存在bool IsPositive(int n) { return n 0; } bool IsPositive(double x) { return x 0; }写EXPECT_PRED1(IsPositive, 5);会编译报错但这样写可以工作EXPECT_PRED1(*static_castbool (*)(int)*(IsPositive), 5);static_cast尖括号里的内容正是IsPositive()的int版本的函数指针类型。另一个例子模板函数template typename T bool IsNegative(T x) { return x 0; }可以在谓词断言中这样使用ASSERT_PRED1(IsNegative*int*, -5);模板参数超过一个时更有趣下面不会编译ASSERT_PRED2(*GreaterThanint, int*, 5, 0);因为 C 预处理器认为你给了ASSERT_PRED24 个参数多了一个。变通办法是把谓词函数用括号包起来ASSERT_PRED2(*(GreaterThanint, int)*, 5, 0);8.2RUN_ALL_TESTS()的返回值不能忽略有人忽略RUN_ALL_TESTS()的返回值即写RUN_ALL_TESTS();而不是return RUN_ALL_TESTS();这是错误的且有危险的测试运行器需要看到RUN_ALL_TESTS()的返回值才能判断测试是否通过。如果main()忽略它即使存在 GTest 断言失败你的测试也会被判定为成功——非常糟糕。为帮助避免这一危险 bugRUN_ALL_TESTS()的实现会让 gcc 在返回值被忽略时发出警告。见到该警告时修复很简单确保它的值被用作main()的返回值。8.3 构造函数/析构函数中 cannot return a value由于 C 的怪癖为了支持向ASSERT_*流式输出消息的语法例如ASSERT_EQ(1, Foo()) blah blah foo;官方不得不放弃在构造函数与析构函数中使用ASSERT*与FAIL*EXPECT*与ADD_FAILURE*不受影响。变通办法是把构造/析构函数的内容移到一个私有的 void 成员函数中或者在可行的话改用EXPECT_*()。8.4 GTest 输出被日志淹没怎么办GTest 的输出设计为简洁、对人类友好的报告。如果你的测试自身产生文本输出会与 GTest 输出混在一起难以阅读。解法大多数日志消息走 stderr因此官方决定让 GTest 输出走 stdout从而可以方便地用重定向分离两者例如./my_test googletest_output.txt九、并行与多线程测试的定位GTest 是否支持并行运行测试测试运行器往往与构建/测试环境紧耦合GTest 不试图解决并行运行测试的问题而是力求与测试运行器良好配合。例如GTest 的 XML 报告包含每个测试花费的时间gtest_list_tests与gtest_filter标志可用于把测试方法的执行切分到多个进程。这些功能可以帮助测试运行器实现并行。为什么不用不同线程加速编写线程安全代码很难。大多数测试没有按线程安全来写因此在多线程环境下可能不工作。进一步想当你知道其他线程在做什么时都很难让代码正确更不用说不知道其他线程在做什么测试方法可能在你写测试之后被添加、删除或修改的情况。如果想并行运行测试最好在不同进程中运行。十、Windows/Visual Studio 相关问题10.1 内存泄漏消息由于静态初始化的 GTest 单例需要在堆上分配内存Visual C 的内存泄漏检测器会在程序结束时报告内存泄漏。避免的最简单方式是使用_CrtMemCheckpoint与_CrtMemDumpAllObjectsSince调用使静态初始化的堆对象不被报告。更多堆检查/调试例程参见 MSDN。10.2 链接错误LNK2005 / LNK4217 / LNK4049当你用 GTest 库链接测试工程、而工程与库未使用相同编译器设置时可能出现以下链接错误或警告LNK2005: symbol already defined in objectLNK4217: locally defined symbol symbol imported in function functionLNK4049: locally defined symbol symbol importedGTest 工程gtest.vcproj本仓库即 msvc/gtest.vcproj的 Runtime Library 选项设为 /MT使用多线程静态库debug 用 /MTd。如果你的工程用别的例如 /MD多线程 DLLdebug 用 /MDd就需要修改 GTest 工程的设置使之与你的工程一致。修改方法在 Visual Studio IDE 中打开工程属性选择Configuration Properties | C/C | Code Generation分支修改 Runtime Library 选项。也可以尝试用gtest-md.vcproj本仓库对应 msvc/gtest-md.vcproj替代gtest.vcproj——-md 后缀的工程使用 DLL 版 Microsoft 运行库/MD 或 /MDd无后缀的静态运行库版/MT 或 /MTd二者必须与测试代码使用同一选项。10.3 测试放在库里GTest 不运行它们FAQ 提示是否读过 V1_5_Primer.md 页面上给 Visual C 用户的重要提示import important note for Visual C users该警告解释了在 MSVC 下以库形式存放测试时符号被裁剪、测试不被注册的问题。10.4 用 Visual Studio 使用 GTest 从何入手FAQ 提到有用户在邮件列表发布了其解决方案博客GTest 入门帮助。在本仓库中可参考的本地资源是 README.md 中Legacy Build Scripts一节打开msvc/文件夹下的gtest.sln或gtest-md.sln用 Visual Studio 构建 GTest-md后缀文件使用 DLL 版运行库/MD 或 /MDd无后缀使用静态版/MT 或 /MTd。注意 gtest 与测试代码必须用同一选项编译Visual Studio 2005 及以上版本推荐 -md 版因为 /MD 是新版 VS 新建工程的默认。十一、IDE 集成与其他使用提示在 Emacs 中跳转到失败行GTest 的失败消息格式被 Emacs 与许多其他 IDE如 acme、XCode所识别。若 GTest 消息在 Emacs 的编译缓冲区中它是可点击的——对消息按enter可跳到对应源码或用 C-x 跳到下一个失败处。测试运行器友好接口结合第五、九节gtest_list_tests输出测试清单、gtest_filter按模式筛选、XML 报告含每测试耗时三者组合即外部并行化所需的全部信息。十二、问题求助渠道与提问规范V1.5 FAQ 给出的求助资源均为 GTest 社区渠道不在本仓库内阅读 GTest wiki 的其他页面搜索 GTest 邮件列表归档在 googletestframework 邮件组提问为防止垃圾邮件需要先加入该讨论组才能发帖。注意在 issue tracker 里开 issue 不是获取答案的好途径因为查看它的人很少、频率很低。提问时尽量提供以下尽可能多的信息信息不足则无人能帮你你使用的 GTest 版本若直接从 SVN 检出给出修订号GTest 处于活跃开发中你的问题可能已在后续版本解决你的操作系统编译器名称与版本给编译器的完整命令行标志完整的编译器错误消息如果问题是关于编译出问题的实际代码理想情况下是最小但完整的程序。附录本文引用的仓库证据索引FAQ 论点仓库证据死亡测试经fork()派生子进程gtest-death-test.cc#L853、gtest-death-test.cc#L1100-L1105threadsafe/fast两种--gtest_death_test_stylegtest-death-test.cc#L90、gtest-death-test.cc#L1198-L1215死亡测试自测用例test/gtest-death-test_test.ccVS2008 x64 流程涉及的解决方案/工程msvc/gtest.sln、msvc/gtest-md.sln、msvc/gtest.vcproj、msvc/gtest-md.vcprojfixture 继承完整示例samples/sample5_unittest.cc构建方式与-md/静态运行库区分README.md、CMakeLists.txt配套入门/高级指南V1_5_Primer.md、V1_5_AdvancedGuide.md说明以上所有结论均出自 V1_5_FAQ.md 原文及其在本仓库v8_5_7/testing/gtest/目录下的可验证源码与工程文件FAQ 中提及的 V1.5 时期的数据驱动测试支持缺口、断言宏未来可能抛异常的规划均为原文档的当时表述后续版本V1.6/V1.7 文档已并存于 docs/ 目录的行为请以对应版本文档为准。【免费下载链接】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),仅供参考
返回列表