miniblink49 内置的 Google Test 1.5 FAQ:从断言机制、死亡测试到 Visual Studio 构建实战
【免费下载链接】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_FAQ.md 展开。这篇 FAQ 回答了使用 Google Test 写 C++ 单元测试时最常被问到的三十多个问题,涵盖断言宏的设计取舍、死亡测试(death test)的子进程机制、测试夹具(fixture)的组织方式,以及 Windows 下用 Visual Studio 构建 gtest 库时的典型链接错误。读完本文,你不仅能理解 miniblink49 所依赖的这份测试框架(见 v8_6_7/testing/gtest 目录)的设计原理,还能直接套用其中给出的构建与排错步骤。
一、为什么选择 Google Test 而不是其他 C++ 测试框架
FAQ 开篇就明确表态:不存在(也不会有)唯一最好的 C++ 测试框架,需要为具体任务挑选合适的工具。Google Test 是作者团队找不到“特性与便利性的合适组合”而自建的,以下这些特性并不为它独有,但它们的组合构成了选择它的理由:
- 可移植性强:它能在许多 STL 类型(如
std::string、std::vector)无法编译的环境下工作,不要求异常和 RTTI,因此可以运行在 Linux、Mac OS X、Windows 以及若干嵌入式操作系统上。 - 非致命断言(
EXPECT_*)节省时间:它允许单个测试在一次“编辑—编译—测试”循环中报告多处失败。 - 流式错误信息:用流语法即可附加信息,例如
ASSERT_EQ(5, Foo(i)) << " where i = " << i;,不需要额外的宏或特殊函数。 - 测试自动注册:框架自动发现测试,无需手工枚举才能运行。
- 可扩展的断言词汇表:提供
EXPECT_PRED*系列,可以轻松地在其上定义自己的断言宏。 - 死亡测试:方便确保生产代码中的 assert 被正确的条件触发。
SCOPED_TRACE:当断言失败发生在子例程或循环内部时,帮助理解失败上下文。- 按名称模式筛选测试:快速复现某个失败时可以只跑指定测试。
二、Windows 平台构建专题
在 Visual Studio 2008 中生成 64 位二进制
FAQ 给出了完整的 VS2008 x64 构建流程(本仓库 msvc 目录下也确实带有对应的解决方案文件,如 msvc/gtest.sln、msvc/gtest-md.sln):
- 加载提供的解决方案文件
msvc\gtest-md.sln或msvc\gtest.sln,通过迁移向导升级到 VS2008; - 从“Build”菜单打开
Configuration Manager...,在Active solution platform下拉框中点<New...>; - 新平台选择
x64,Copy settings from保持Win32,勾选Create new project platforms,确定。此时标准工具栏上可以切换Win32/x64(也可用 Batch Build 一次构建两者); - 为防止不同平台的中间产物互相覆盖,需要修改新平台的
Intermediate Directory:在 Solution Explorer 中按住 Shift 多选所有工程(不含解决方案本身),右键 →Properties,左侧选Configuration Properties,Configuration下拉选All Configurations,确认平台为x64,把Intermediate Directory从$(PlatformName)\$(ConfigurationName)改为$(OutDir)\$(ProjectName); - 重新构建后,64 位二进制文件位于
msvc\x64\Debug目录。
能否在 MinGW 上有
官方没有亲自验证,但有用户报告:从 Cygwin 使用 MinGW 时可以成功编译安装,配置命令为:
PATH/TO/configure CC="gcc -mno-cygwin" CXX="g++ -mno-cygwin"也可以尝试把-mno-cygwin换成直接链接真实的 MinGW 二进制(官方未尝试)。注意事项:
- 编译时会有大量警告;
make check会报一些错误,因为并非所有针对 Google Test 自身的测试都兼容 MinGW。
另有报告称在 Linux 上交叉编译 MinGW 二进制是可行的(原 FAQ 引用的 WxWidgets 交叉编译外部链接已失效,此处不再给出)。
为什么会出现一批链接错误(LNK2005 / LNK4217 / LNK4049)
如果你把测试工程链接 gtest 库后报如下错误:
LNK2005: symbol already defined in objectLNK4217: locally defined symbol 'symbol' imported in function 'function'LNK4049: locally defined symbol 'symbol' imported
原因是你的工程与 gtest 库的编译器运行时设置不一致。gtest 的工程文件(见 msvc/gtest.vcproj)将 Runtime Library 设为/MT(调试为/MTd)。如果你的工程用了/MD(调试为/MDd),需要打开项目属性,在Configuration Properties | C/C++ | Code Generation下把 "Runtime Library" 改成与 gtest 一致;或者直接使用gtest-md.vcproj代替gtest.vcproj。
把测试放进静态库后 Google Test 不运行它们
这是 Primer 中的“Important note for Visual C++ users” 一节专门讲的问题:如果main()与测试分属不同库,Visual C++ 链接器可能把“无人引用”的测试注册对象直接裁剪掉。解决方式是:
- 在测试库代码中声明并在主程序中引用一个函数,强制链接器保留整个库;
- 若测试定义在静态库中,给主程序链接器加上
/OPT:NOREF(VS 中即工程属性 → Linker → Optimization → References 设为Keep Unreferenced Data (/OPT:NOREF))。
如何在 Windows 上抑制内存泄漏消息
静态初始化的 Google Test 单例需要堆分配,VC++ 的内存泄漏检测器会在程序结束时报告“泄漏”。最简单的规避方法是用_CrtMemCheckpoint和_CrtMemDumpAllObjectsSince,避免报告静态初始化对象;更多堆检查/调试例程可查阅 MSDN。
三、断言宏的设计取舍
为什么支持 EXPECT_EQ(NULL, ptr) 却不支持 EXPECT_NE(NULL, ptr)
由于 C++ 的某些特性,把NULL作为EXPECT_XX()/ASSERT_XX()的参数需要非平凡的模板元编程技巧,因此只在最需要的地方做:
EXPECT_EQ()约定第一个参数是期望值、第二个是实际值。写EXPECT_EQ(NULL, some_expression)是常见需求,故予以实现。EXPECT_NE(NULL, ptr)的需求弱得多:断言失败时你已知ptr必为NULL,再打印它没有额外信息量,EXPECT_TRUE(ptr != NULL)效果相同。- 若支持
EXPECT_NE(NULL, ptr),为了一致性还得支持EXPECT_NE(ptr, NULL)(EXPECT_NE对两个参数顺序没有约定),模板技巧要在实现里用两次,可读性与可维护性成本更高,收益不抵成本。 - 随着 Google Mock 的 matcher 库发展,官方更鼓励使用统一的
EXPECT_THAT(value, matcher)语法——matcher 可以方便地组合成新 matcher,而EXPECT_NE等宏难以组合。本仓库中 matcher 相关用法可参考 gmock 的 CookBook。
为什么断言不用异常实现
最初动机是能在禁用异常的项目中使用 Google Test,后来发现这种实现还有额外收益:
- 在析构函数中抛出是 C++ 的未定义行为。不用异常意味着断言可以安全地用在析构函数中;
EXPECT_*家族在失败后继续执行,允许单次运行报告多个失败。C++ 编辑—编译—测试循环很长,一次修多个问题非常宝贵;- 若用异常实现断言,用户代码中的
catch可能误吞断言失败:
try { ... ASSERT_TRUE(...) ... } catch (...) { ... }上述代码即使ASSERT_TRUE抛了异常也会“通过”。测试代码里少有人这样写,但在被被测代码回调的函数里写断言时可能不知不觉踩到。
不用异常的代价是:ASSERT_*(内部用return实现)只会中止当前函数,而不是当前TEST。
调用 RUN_ALL_TESTS() 时编译器警告 “ignoring return value”
RUN_ALL_TESTS()的返回值是测试是否通过的唯一信号。如果你写:
RUN_ALL_TESTS(); // 错误:忽略返回值而不是return RUN_ALL_TESTS();,即使存在断言失败,测试也会被外部测试运行器判定为成功——这是很糟糕的。
为了阻止这类危险 bug,RUN_ALL_TESTS()的实现让 gcc 在返回值被忽略时发出警告。在本仓库源码中可以验证这一点:gtest.h 中声明为int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_;,而 gtest-port.h 第 894 行定义:
# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result))所以看到这个警告时,修复方法很简单:让main()返回RUN_ALL_TESTS()的返回值。
编译器提示 “void value not ignored as it ought to be”
多半是你在一个非void返回值的函数里使用了ASSERT_*()。ASSERT_*()只能用在void函数中。
构造函数/析构函数里不能用 ASSERT_*,提示 “cannot return a value”
由于 C++ 的特性,为了支持断言的流式消息语法:
ASSERT_EQ(1, Foo()) << "blah blah" << foo;Google Test 不得不放弃在构造/析构函数中使用ASSERT*与FAIL*(EXPECT*和ADD_FAILURE*不受限)。变通办法:把构造/析构函数的内容移到一个 private 的 void 成员函数里,或者改用EXPECT_*()(如果场景允许)。AdvancedGuide 的 “Assertion Placement” 一节有详细说明。
ASSERT_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_cast<bool (*)(int)>*(IsPositive), 5);尖括号内是int版IsPositive()的函数指针类型。对模板函数:
template <typename T> bool IsNegative(T x) { return x < 0; }可以这样使用:
ASSERT_PRED1(IsNegative*<int>*, -5);模板参数多于一个时更微妙,下面这种写法不能编译:
ASSERT_PRED2(*GreaterThan<int, int>*, 5, 0);因为 C++ 预处理器认为你给ASSERT_PRED2传了 4 个参数(多了一个)。解决办法是用括号把谓词包起来:
ASSERT_PRED2(*(GreaterThan<int, int>)*, 5, 0);断言中使用自定义类型报 “no match for 'operator<<'”
如果你在断言里使用自定义类型FooType,必须保证存在
std::ostream& operator<<(std::ostream&, const FooType&);而且如果FooType声明在某个命名空间里,<<运算符也必须定义在同一个命名空间中(靠参数名查找)。
类体里定义了 static const 成员,却报 “undefined references”
如果你的类有静态数据成员:
// foo.h class Foo { ... static const int kBar = 100; };你还必须在类体外(foo.cc中)定义它:
const int Foo::kBar; // 此处不要带初始化器。否则代码是不合法的 C++,可能以各种意想不到的方式出错——特别是用在EXPECT_EQ等比较断言中时,会直接产生 “undefined reference” 链接错误。
SetUp 函数没有被调用
C++ 区分大小写。正确拼写是SetUp(),你是不是写成了Setup()?同理,也有人把SetUpTestCase()拼成SetupTestCase()然后纳闷它为何永远不被调用。
四、死亡测试(Death Test)深入解析
死亡测试在 miniblink49 这种对崩溃敏感的内核代码测试中尤为有用。FAQ 对它的机制、命名规则和常见故障解释得相当透彻,底层实现可对照 gtest-death-test.cc。
为什么死亡测试用断言实现而不是测试运行器
目标是让死亡测试尽可能方便:
- 运行器风格要求把信息拆成两半:死亡测试本身的定义,以及告诉运行器如何执行、期望什么的规格说明。前者用 C++ 写,后者未必,用户必须小心保持两者同步。而
ASSERT_DEATH(statement, expected_message)在同一处、用同一种语言给出全部信息,无需样板代码,非常声明式; ASSERT_DEATH与 Google Test 其他断言语法和错误报告语义一致,容易学;ASSERT_DEATH可以与其他断言、其他逻辑任意混合,不受“一个测试方法只能一个死亡测试”的限制:
if (FooCondition()) { ASSERT_DEATH(Bar(), "blah"); } else { ASSERT_EQ(5, Bar()); }ASSERT_DEATH可以引用当前函数的局部变量,还可以根据运行时信息决定要写多少死亡测试:
const int count = GetCount(); // 只有运行时才知道。 for (int i = 1; i <= count; i++) { ASSERT_DEATH({ double* buffer = new double[i]; ... 初始化 buffer ... Foo(buffer, i) }, "blah blah"); }运行器风格通常更静态、更不灵活,或需要更多用户努力才能达到同等灵活性。
另外ASSERT_DEATH调用fork()创建子进程来跑死亡测试——fork()使用写时复制(copy-on-write)页,开销几乎为零,而且子进程从用户给定的语句直接开始执行,跳过所有全局/局部初始化和到达该语句之前的任何代码。若从零启动子进程,当测试动态链接了大量库时,光加载就可能耗时数秒。
死亡测试修改的状态为何“消失”
死亡测试(EXPECT_DEATH等)在子进程中执行,好让预期的崩溃不会杀死测试程序(即父进程)。因此它们产生的任何内存中的副作用只能在各自的子进程中被观察到,父进程看不到——可以粗略理解为它们运行在“平行宇宙”里。
ASSERT_DEATH 的 statement 参数可以写什么
ASSERT_DEATH(_statement_, _regex_)(或任何死亡断言宏)可以出现在_statement_合法的任意位置,即它可以是当前上下文中任何有意义的 C++ 语句,可以引用全局和/或局部变量,可以是简单函数调用(常见情况)、复杂表达式或复合语句:
// 死亡测试可以是一个简单函数调用。 TEST(MyDeathTest, FunctionCall) { ASSERT_DEATH(Xyz(5), "Xyz failed"); } // 也可以是引用变量和函数的复杂表达式。 TEST(MyDeathTest, ComplexExpression) { const bool c = Condition(); ASSERT_DEATH((c ? Func1(0) : object2.Method("test")), "(Func1|Method) failed"); } // 死亡断言可以出现在函数的任何位置,包括循环内。 TEST(MyDeathTest, InsideLoop) { // 验证 Foo(0)、Foo(1)、...、Foo(4) 都会死亡。 for (int i = 0; i < 5; i++) { EXPECT_DEATH_M(Foo(i), "Foo has \\d+ errors", ::testing::Message() << "where i is " << i); } } // 死亡断言可以包含复合语句。 TEST(MyDeathTest, CompoundStatement) { // 验证 Bar(0)、Bar(1)、...、Bar(4) 中至少有一个死亡。 ASSERT_DEATH({ for (int i = 0; i < 5; i++) { Bar(i); } }, "Bar has \\d+ errors");}googletest_unittest.cc 里还有更多例子。
死亡测试的正则表达式语法
在 POSIX 系统上,Google Test 使用 POSIX 扩展正则表达式语法;在 Windows 上使用一种受限的正则语法变体。细节见 AdvancedGuide 的 “Regular Expression Syntax” 一节。
死亡测试挂起(或段错误)怎么办
Google Test 中死亡测试在子进程中运行,机制相当精巧,写死亡测试前务必先理解其工作原理(见 AdvancedGuide 的 “Death Tests” 一章)。排查思路:
- 死亡测试不喜欢父进程中有多个线程。首先尝试消除在
EXPECT_DEATH()之外创建线程的代码; - 如果某些必须使用的库在
main()之前就创建了线程,这有时不可避免。可以尝试把尽可能多的活动移入EXPECT_DEATH()(极端情况是全部移入),或让它里面留下的东西尽可能少;也可以把死亡测试风格设为"threadsafe"——更安全但更慢——看是否有帮助; - 如果用线程安全死亡测试,请记住子进程会从头重新运行整个测试程序,因此必须保证你的程序能与其自身并排运行且是确定性的;
- 归根结底这是良好的并发编程问题:必须确保程序没有竞态条件或死锁。没有银弹。
源码中两种风格的描述与 gtest-death-test.cc 对应:默认风格为"fast"(子进程 fork 后立即执行死亡测试),"threadsafe"(子进程重新执行测试二进制文件),Windows 上fast会被视为等价于threadsafe。
为什么 ASSERT_DEATH 抱怨“已经 join 过的旧线程”
在 Linux 的 pthread 库中,从单线程跨入多线程就不可回头:第一次创建线程时,会额外创建一个管理器线程,于是你有的是 3 个而不是 2 个线程。之后你创建的线程 join 回主线程后,线程数只减 1,但管理器线程永远不会被杀死,所以你仍有 2 个线程——意味着不能安全地运行死亡测试。新的 NPTL 线程库没有这个问题(不创建管理器线程),但如果你不能控制测试运行在哪台机器上,不要依赖这一点。
为什么使用 ASSERT_DEATH 时整个测试用例都必须命名为 FOODeathTest
Google Test 不会交错运行不同测试用例的测试:它先跑完一个测试用例里的全部测试,再跑下一个。原因是测试用例需要先 setup 再跑第一个测试、结束后再 teardown,拆散测试用例需要多次 setup/teardown,既低效又使语义不清。
如果按“测试名”而不是“测试用例名”来排序测试,就会出问题:
TEST_F(FooTest, AbcDeathTest) { ... } TEST_F(FooTest, Uvw) { ... } TEST_F(BarTest, DefDeathTest) { ... } TEST_F(BarTest, Xyz) { ... }因为FooTest.AbcDeathTest要先于BarTest.Xyz运行,且又不交错测试用例,就必须把FooTest的所有测试都跑到BarTest之前——这与BarTest.DefDeathTest要先于FooTest.Uvw的要求相矛盾。
如果嫌整个测试用例都叫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(...) ... }有 fixture 类 Foo,但 TEST_F(Foo, Bar) 报 “no matching function for call to Foo::Foo()”
Google Test 必须能创建你的测试夹具对象,因此它需要默认构造函数。通常需要你自己定义的情况:
- 你显式声明了
Foo的非默认构造函数,那么即便默认构造函数是空的也必须定义; - 如果
Foo有 const 非静态数据成员,你必须定义默认构造函数并在初始化列表中初始化该 const 成员(早期 gcc 不强制初始化 const 成员,这是已在 gcc 4 中修复的 bug)。
五、测试夹具(Fixture)的组织方式
为什么有/无夹具要用两个不同的宏(TEST 与 TEST_F)
遗憾的是,C++ 宏系统不允许用同一个宏同时覆盖两种情况。一种可能是只提供带夹具的宏,要求用户有时定义空夹具:
class FooTest : public ::testing::Test {}; TEST_F(FooTest, DoesThis) { ... }或者:
typedef ::testing::Test FooTest; TEST_F(FooTest, DoesThat) { ... }但很多人觉得这多出了一行。:-) 目标是让写测试真正容易,所以让最简单的测试创建起来尽可能平凡——于是用了单独的宏。两种做法都不理想,但都合理,最终差别不大。
为什么不用 struct 作测试夹具
struct 只在表示“被动数据”时使用。struct 与 class 的这种区分有助于表达作者意图。测试夹具带有SetUp()、TearDown()这类逻辑,因此更适合定义为 class。
能否从一个夹具派生另一个夹具
可以。每个测试夹具对应一个同名的测试用例,意味着一个夹具只能被一个测试用例使用。但有时多个测试用例想用相同或略有不同的夹具(例如验证 GUI 库的所有测试用例都不泄漏字体、画笔等系统资源)。做法是把共享逻辑放进基类夹具,再为每个用例从基类派生一个夹具,然后用TEST_F()写各自的测试:
// 定义基类测试夹具。 class BaseTest : public ::testing::Test { protected: ... }; // 从 BaseTest 派生夹具 FooTest。 class FooTest : public BaseTest { protected: virtual void SetUp() { BaseTest::SetUp(); // 先设置基类夹具。 ... FooTest 的额外设置 ... } virtual void TearDown() { ... FooTest 的清理工作 ... BaseTest::TearDown(); // 记住在清理完 FooTest 之后再拆除基类夹具! } ... FooTest 的函数与变量 ... }; // 使用夹具 FooTest 的测试。 TEST_F(FooTest, Bar) { ... } TEST_F(FooTest, Baz) { ... } ... 其他从 BaseTest 派生的夹具 ...必要时还可以继续从派生夹具派生,Google Test 对继承层数没有上限。完整示例见 samples/sample5_unittest.cc。
多个用例共享同一夹具逻辑,必须为每个用例都定义新的夹具类吗
不必。与其为每个用例写空壳类:
class FooTest : public BaseTest {}; TEST_F(FooTest, Abc) { ... } TEST_F(FooTest, Def) { ... } class BarTest : public BaseTest {}; TEST_F(BarTest, Abc) { ... } TEST_F(BarTest, Def) { ... }直接 typedef 即可:
typedef BaseTest FooTest; TEST_F(FooTest, Abc) { ... } TEST_F(FooTest, Def) { ... } typedef BaseTest BarTest; TEST_F(BarTest, Abc) { ... } TEST_F(BarTest, Def) { ... }该用夹具的构造/析构函数,还是 SetUp/TearDown
首先记住:Google Test不会在多个测试之间复用同一个夹具对象。对每个TEST_F,它会创建新的夹具对象,立即调用SetUp()、运行测试、调用TearDown(),然后立即删除夹具对象。因此,如果构造/析构函数已经完成了工作,就没有必要再写SetUp()/TearDown()。
仍建议用SetUp()/TearDown()的情形:
- 如果拆除操作可能抛异常,必须用
TearDown()而不是析构函数——析构函数中抛出是未定义行为,通常直接杀死程序。注意许多标准库(如 STL)在编译器开启异常时可能抛异常;因此若希望写“有无异常都能跑”的可移植测试,应优先TearDown(); - Google Test 团队考虑在开启异常的平台(如 Windows、Mac OS、Linux 客户端)上让断言宏抛出异常,以省去用户从子例程向调用者传播失败的负担。因此如果你的代码可能运行在这类平台,不要在析构函数中使用 Google Test 断言;
- 在构造/析构函数中无法对
this调用虚函数(可以调用虚方法,但会被静态绑定)。如果需要调用会被派生类重写的方法,必须用SetUp()/TearDown()。
为什么优先用夹具而不是全局变量
- 测试很可能要修改其全局变量的状态,这会让副作用从一个测试逃逸、污染其他测试,难以调试。用夹具时,每个测试都有一组全新的、同名但相互独立的变量,测试之间保持独立;
- 全局变量污染全局命名空间;
- 测试夹具可以通过子类化复用,全局变量做不到——这对多个测试用例有共同需求时很有用。
六、测试私有成员而不用 FRIEND_TEST
首先应努力写出可测试的代码:类应当能从其公有接口被轻松测试,Pimpl 惯用法(把所有私有成员移进一个 helper 类,并把 helper 类的所有成员设为 public)就是一种手段。除此之外,不用FRIEND_TEST还有几种选择:
1. 把测试写成夹具类的成员:
class Foo { friend class FooTest; ... }; class FooTest : public ::testing::Test { protected: ... void Test1() {...} // 这里可以访问 Foo 的私有成员。 void Test2() {...} // 这里也可以。 }; TEST_F(FooTest, Test1) { Test1(); } TEST_F(FooTest, Test2) { Test2(); }2. 在夹具类中写被测类私有成员的访问器:
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) ... }3. 若方法是 protected 的,可在测试专用子类中改变其访问级别:
class YourClass { ... protected: // 为可测试性提供 protected 访问。 int DoSomethingReturningInt(); ... }; // 在 your_class_test.cc 文件中: class TestableYourClass : public YourClass { ... public: using YourClass::DoSomethingReturningInt; // 改变访问权限 ... }; TEST_F(YourClassTest, DoSomethingTest) { TestableYourClass obj; assertEquals(expected_value, obj.DoSomethingReturningInt()); }私有静态成员如何测
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));七、参数化测试与带 main() 的文件
想用不同参数重复跑同一测试,要写好几份拷贝吗
不需要。可以使用 “值参数化测试”(Value-Parameterized Tests):它能让你用不同参数重复运行测试,而不必定义多次。
如何测试定义了 main() 的文件
要测试foo.cc,需要把它编译链接进单元测试程序;但其中定义main()会与单测的main()冲突,导致构建错误。正确做法是拆成三个文件:
foo.h:声明;foo.cc:除main()外的所有定义;foo_main.cc:仅main()的定义。
这样foo.cc就可以轻松测试了。如果是在现有文件上加测试、不想做这种侵入式改动,有个 hack:把整个foo.ccinclude 进单元测试里:
// 文件 foo_unittest.cc // 头文件部分 ... // 重命名 foo.cc 中的 main(),给单测 main() 让路 #define main FooMain #include "a/b/foo.cc" // 测试从这里开始。 ...请记住这只是 hack,应只作为最后手段。
接口有多个实现,能否写一套测试重复跑遍所有实现
Google Test 对此类测试(或更一般的数据驱动测试)的支持还不完善,官方希望尽快改进。
八、输出与工具链技巧
Google Test 的输出被大量日志淹没怎么办
Google Test 的输出本应是一份简洁、对人友好的报告;如果测试自己也产生文本输出,两者混在一起就难以阅读。由于大多数日志走 stderr,Google Test 的输出被设计为走stdout,这样就可以用重定向分离:
./my_test > googletest_output.txt如何在 Emacs 中直接跳转到失败行
Google Test 的失败消息格式能被 Emacs 及许多其他 IDE(如 acme、XCode)理解。如果 Emacs 的编译缓冲区里有 Google Test 消息,它就是可点击的:按Enter可跳到对应源码,用C-x ``跳到下一个失败。
九、并行与线程模型的两个澄清
Google Test 支持并行运行测试吗?测试运行器往往与构建/测试环境紧密耦合,Google Test 不试图解决“并行运行测试”的问题,而是努力与测试运行器配合良好。例如:它的 XML 报告包含每个测试所花的时间;gtest_list_tests与gtest_filter标志可用于把测试方法的执行拆分到多个进程中,从而帮助测试运行器并行化。
为什么不直接在不同线程里跑测试来加速?写线程安全代码很难,多数测试并未按线程安全来写,多线程环境下可能行为不正确。想清楚就会发现:已知其他线程在做什么时都难保证正确,就更不用说未知情况(测试方法随时可能被添加、删除或修改)。如果要并行跑测试,最好在不同进程中运行。
十、提问渠道与提问规范
本仓库中的 V1_5_FAQ.md 属于 Google Test 1.5 时代的文档,若问题未被覆盖,FAQ 给出的求助渠道是:阅读其他 wiki 页、搜索邮件列表归档、或在 googletestframework 邮件列表上提问(发帖前需先加入讨论组)。FAQ 特别强调:不要在 issue tracker 里提问,那里很少有人、且很不定期地看。
提问时尽量提供以下信息(信息不足别人无法帮忙):
- 你使用的 Google Test 版本(或从 SVN 直接检出的修订号)——该项目处于活跃开发中,问题可能在新版本已解决;
- 你的操作系统;
- 编译器名称和版本;
- 传给编译器的完整命令行参数;
- 完整的编译器错误消息(如果问题是编译相关的);
- 出问题的实际代码(理想情况下是完整的最小程序)。
附:在 miniblink49 仓库中阅读本文档的落点
本文所有结论均来自仓库内的文档与源码,读者可按以下路径继续深入:
- FAQ 本体:v8_6_7/testing/gtest/docs/V1_5_FAQ.md
- 配套教程:V1_5_Primer.md、V1_5_AdvancedGuide.md
RUN_ALL_TESTS()声明与GTEST_MUST_USE_RESULT_:gtest.h、gtest-port.h- 死亡测试实现(fast/threadsafe 风格、fork 机制):gtest-death-test.cc
- 死亡测试的自测用例:gtest-death-test_test.cc
- 派生夹具的完整示例:samples/sample5_unittest.cc
- MSVC 工程文件(/MT 运行时设置来源):msvc/gtest.vcproj
- matcher 用法(EXPECT_THAT):gmock/docs/CookBook.md
需要提醒的适用前提:这份 FAQ 对应的是 Google Test 1.5 时代的实现(随 v8_6_7 的测试工程一同打包进 miniblink49 仓库),其中部分描述(如 pthread 管理器线程、VS2008 迁移向导、MSDN 链接等)带有当时的平台背景;阅读时建议以仓库内 gtest 源码 的实际行为为准。
【免费下载链接】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),仅供参考