GoogleTest 断言宏完全参考:从 EXPECT/ASSERT 家族到浮点、异常与 Death Test 的实现剖析
【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest
GoogleTest 的断言宏(assertion macros)是编写 C++ 单元测试的核心工具,覆盖布尔条件、二元比较、C 字符串、浮点数、异常、谓词以及进程终止等多种验证场景。本文基于当前仓库的 Assertions Reference 逐类完整整理全部宏的用法与语义,并结合 gtest.h 与 gtest-internal.h 中的实现源码,解释“EXPECT_ 与 ASSERT_ 的区别从哪里来”“4 ULP 容差在代码中的位置”等底层细节,帮助你在实际项目中正确选择并深入理解每一个断言宏。
使用断言宏的基本约定
要使用本文涉及的断言宏,需要在测试文件中包含:
#include <gtest/gtest.h>文档中列出的大部分宏都成对出现:EXPECT_变体与ASSERT_变体。二者语义的关键区别是:
EXPECT_宏:失败时生成非致命(nonfatal)失败,当前函数会继续向下执行,后续的断言仍会运行;ASSERT_宏:失败时生成致命(fatal)失败,直接终止当前函数(以return方式离开)。
因此ASSERT_宏只能用于返回void的函数中——如果函数有返回值,return;将导致编译错误。这一约束在使用ASSERT_HRESULT_SUCCEEDED等宏于非 void 函数时尤其需要注意(详见 Advanced Guide 的 Assertion Placement 章节)。
所有断言宏都支持通过<<运算符流式追加自定义失败消息,例如:
EXPECT_TRUE(my_condition) << "My condition is not true";凡是能流向ostream的内容都可以流向断言宏,尤其是 C 字符串和字符串对象。如果向断言流式输出宽字符串(wchar_t*、WindowsUNICODE模式下的TCHAR*、或std::wstring),输出时会被转译为 UTF-8 打印。
从源码结构看,这种流式消息能力由 gtest.h 中的internal::AssertHelper类实现:断言宏展开后构造一个AssertHelper临时对象,<<表达式通过其operator=(const Message&)语义技巧把消息交给断言框架,析构时统一上报。源码注释还特意提到把数据放在独立struct中以压缩AssertHelper对象大小,因为 GCC 不会复用临时变量的栈空间,每个EXPECT_EQ都会为AssertHelper预留栈开销。
显式成功与失败
这一类断言不测试某个值或表达式,而是直接生成成功或失败,适用于由控制流(而非布尔表达式)决定测试成败的场景:
switch (expression) { case 1: ... some checks ... case 2: ... some other checks ... default: FAIL() << "We shouldn't get here."; }SUCCEED()
SUCCEED()
显式生成一个“成功”。注意它并不会让整体测试变为成功——只有测试执行期间没有任何断言失败,该测试才算通过。SUCCEED目前纯粹是文档性质的标记,不产生任何用户可见输出;文档也说明未来可能会在输出中体现SUCCEED消息。
FAIL()
FAIL()
生成一个致命失败,随后从当前函数返回。因此它同样只能用于返回void的函数中(见 Assertion Placement)。
ADD_FAILURE()
ADD_FAILURE()
生成一个非致命失败,当前函数继续运行。
ADD_FAILURE_AT(file_path, line_number)
ADD_FAILURE_AT(file_path,line_number)
在指定的文件和行号位置生成一个非致命失败。适合在封装层把失败“指回”真实的问题源头。
广义断言:EXPECT_THAT 与 Matcher
EXPECT_THAT允许用matcher来验证值,是把断言写成“类英文句子”的关键设施。
EXPECT_THAT(value,matcher)ASSERT_THAT(value,matcher)
验证value是否匹配给定的 matcher。例如:
#include <gmock/gmock.h> using ::testing::AllOf; using ::testing::Gt; using ::testing::Lt; using ::testing::MatchesRegex; using ::testing::StartsWith; ... EXPECT_THAT(value1, StartsWith("Hello")); EXPECT_THAT(value2, MatchesRegex("Line \\d+")); ASSERT_THAT(value3, AllOf(Gt(5), Lt(10)));Matcher 能让这类断言读起来像英文,并在失败时生成信息丰富的消息。如果上面针对value1的断言失败,输出大致如下:
Value of: value1 Actual: "Hi, world!" Expected: starts with "Hello"仓库内配套提供了:
- 内置 matcher 库,参见 Matchers Reference(对应头文件 gmock-matchers.h 与 gtest-matchers.h);
- 自写 matcher 的方法,参见 Writing New Matchers Quickly。
这套机制的思想源自 Joe Walnes 的 Hamcrest 项目(其为 JUnit 提供了assertThat())。
布尔条件断言
EXPECT_TRUE(condition)/ASSERT_TRUE(condition)
验证condition为真。
EXPECT_FALSE(condition)/ASSERT_FALSE(condition)
验证condition为假。
二者是所有“值断言”的最简形态,失败消息会直接打印表达式的值。
二元比较断言
这一类断言比较两个值。值参数必须支持该断言的比较运算符,否则会在编译期报错。
通用规则(文档逐条列出,务必注意):
- 若参数支持
<<运算符,断言失败时会用它打印参数值;否则 GoogleTest 会尽力以最佳方式打印——参见 Teaching GoogleTest How to Print Your Values; - 参数恰好求值一次,因此参数带副作用也是安全的;但参数求值顺序未定义,程序不应依赖任何特定顺序;
- 同时支持窄字符串与宽字符串对象(
string与wstring); - 比较浮点数请改用下文 浮点比较 断言,避免舍入误差问题。
EXPECT_EQ / ASSERT_EQ
EXPECT_EQ(val1,val2)验证val1 == val2。
- 对指针执行指针相等性(地址比较)。如果用在两个 C 字符串上,测试的是它们是否位于同一内存位置,而不是内容是否相同。按内容比较 C 字符串请改用
EXPECT_STREQ。 - 与
NULL比较指针时,推荐写EXPECT_EQ(ptr, nullptr)而不是EXPECT_EQ(ptr, NULL)。
源码印证:EXPECT_EQ最终调用 gtest.h 中的internal::CmpHelperEQ,即一次普通的lhs == rhs比较;失败路径被拆分到CmpHelperEQFailure(L1378-L1385)以减小热路径栈帧,注释中说明这是为了降低在紧密循环中调用EXPECT_*时某些 sanitizer 的开销。此外EqHelper::Compare提供了专门的std::nullptr_t重载(L1433-L1441),这正是文档推荐nullptr而非NULL/0的原因:nullptr重载能确定性地走指针比较路径。
EXPECT_NE / ASSERT_NE
EXPECT_NE(val1,val2)验证val1 != val2。
与EXPECT_EQ相同的指针语义:用于两个 C 字符串时比较的是内存位置是否不同,而非内容是否不同;按内容比较请用EXPECT_STRNE。与NULL比较指针时推荐EXPECT_NE(ptr, nullptr)。
EXPECT_LT / EXPECT_LE / EXPECT_GT / EXPECT_GE
EXPECT_LT(val1,val2)/ASSERT_LT(...):验证val1 < val2EXPECT_LE(val1,val2)/ASSERT_LE(...):验证val1 <= val2EXPECT_GT(val1,val2)/ASSERT_GT(...):验证val1 > val2EXPECT_GE(val1,val2)/ASSERT_GE(...):验证val1 >= val2
从源码结构看,这五个比较宏共用 gtest.h 中GTEST_IMPL_CMP_HELPER_(op_name, op)宏生成的CmpHelperNE/LE/LT/GE/GT系列辅助函数:比较成功返回AssertionSuccess(),失败则通过CmpHelperOpFailure生成形如Expected: (a) < (b), actual: ... vs ...的消息(L1447-L1455),其中两侧值都经过FormatForComparisonFailureMessage格式化,保证失败输出可读。
C 字符串比较断言
这一类断言比较两个C 字符串(const char*等)。比较两个std::string对象时应改用EXPECT_EQ/EXPECT_NE。
补充语义(来自文档):
- 也接受宽 C 字符串(
wchar_t*);两个宽字符串比较失败时,值会以 UTF-8 窄字符串形式打印; - 要把 C 字符串与
NULL比较,使用EXPECT_EQ(c_string, nullptr)或EXPECT_NE(c_string, nullptr)。
| 宏 | 语义 |
|---|---|
EXPECT_STREQ(str1, str2)/ASSERT_STREQ(...) | 两个 C 字符串内容相同 |
EXPECT_STRNE(str1, str2)/ASSERT_STRNE(...) | 两个 C 字符串内容不同 |
EXPECT_STRCASEEQ(str1, str2)/ASSERT_STRCASEEQ(...) | 内容相同(忽略大小写) |
EXPECT_STRCASENE(str1, str2)/ASSERT_STRCASENE(...) | 内容不同(忽略大小写) |
源码中这些宏对应 gtest.h 里声明的CmpHelperSTREQ/CmpHelperSTRNE/CmpHelperSTRCASEEQ/CmpHelperSTRCASENE,并针对wchar_t*提供宽字符重载(受GTEST_HAS_STD_WSTRING宏门控),与文档所述宽字符串支持一致。
浮点比较断言
由于舍入误差,两个浮点值精确相等的可能性极低,因此EXPECT_EQ不适合比较浮点数。有意义的浮点比较通常需要用户仔细选择误差界。GoogleTest 还提供了基于ULP(Units in the Last Place,最后一位的单位数)的默认误差界断言。
EXPECT_FLOAT_EQ / ASSERT_FLOAT_EQ
验证两个float值近似相等,容差为相互之间不超过 4 个 ULP。实现细节在 gtest-internal.h:FloatingPoint类中static const uint32_t kMaxUlps = 4;,AlmostEquals()(L337-L348)把浮点数规范化为“符号-幅度”整数表示后,检查两者距离是否不超过kMaxUlps。宏入口为 gtest.h 的ASSERT_FLOAT_EQ/EXPECT_FLOAT_EQ,底层调用 CmpHelperFloatingPointEQ。
文档特别指出:Infinity与最大有限float值被视为相距 1 个 ULP。
EXPECT_DOUBLE_EQ / ASSERT_DOUBLE_EQ
同上,但面向double:两个double值在相互 4 个 ULP 范围内视为相等;Infinity与最大有限double值视为相距 1 个 ULP。
EXPECT_NEAR / ASSERT_NEAR
EXPECT_NEAR(val1,val2,abs_error)
验证val1与val2的差不超过绝对误差界abs_error。特殊值语义:
- 若两值同为同号无穷,差视为 0;
- 否则只要任一值为无穷,差视为无穷;
- 所有非 NaN 值(含无穷)都不超过一个“无穷”的
abs_error。
实现上对应 gtest.h 中声明的internal::DoubleNearPredFormat辅助函数。
异常断言
这一类断言验证一段代码是否抛出异常,使用前提是编译环境启用了异常(C++ 默认开启;C 或禁用异常的构建不可用)。
被测试的代码可以是复合语句,例如:
EXPECT_NO_THROW({ int n = 5; DoSomething(&n); });EXPECT_THROW / ASSERT_THROW
EXPECT_THROW(statement,exception_type)
验证statement抛出类型恰为exception_type的异常。
EXPECT_ANY_THROW / ASSERT_ANY_THROW
EXPECT_ANY_THROW(statement)
验证statement抛出任意类型的异常。
EXPECT_NO_THROW / ASSERT_NO_THROW
EXPECT_NO_THROW(statement)
验证statement不抛出任何异常。
仓库自带测试 gtest_assert_by_exception_test.cc 专门覆盖“以异常替代 abort 报告失败”的相关行为,可作为异常路径行为的参考实现。
谓词断言
谓词断言允许验证更复杂的谓词,同时比单独使用EXPECT_TRUE产生更清晰的失败消息。
EXPECT_PRED1~5 / ASSERT_PRED1~5
EXPECT_PRED1(pred, val1) EXPECT_PRED2(pred, val1, val2) EXPECT_PRED3(pred, val1, val2, val3) EXPECT_PRED4(pred, val1, ..., val4) EXPECT_PRED5(pred, val1, ..., val5) // ASSERT_PRED1 ~ ASSERT_PRED5 同理验证谓词pred接收给定值作为实参时返回true。pred是函数或仿函数,接受的参数个数与宏接收的值个数一致:对给定实参返回true则断言成功,否则失败。断言失败时会打印每个实参的值;实参恰好求值一次。
示例:
// 当 m 和 n 除 1 之外没有公因子时返回 true。 bool MutuallyPrime(int m, int n) { ... } ... const int a = 3; const int b = 4; const int c = 10; ... EXPECT_PRED2(MutuallyPrime, a, b); // 成功 EXPECT_PRED2(MutuallyPrime, b, c); // 失败第二条断言失败时输出:
MutuallyPrime(b, c) is false, where b is 4 c is 10文档特别提示了重载/模板谓词的歧义问题:
- 若谓词是重载函数或函数模板,宏可能无法确定用哪个版本,需要显式指定函数类型,例如对
int/double两个重载的IsPositive():
EXPECT_PRED1(static_cast<bool (*)(int)>(IsPositive), 5); EXPECT_PRED1(static_cast<bool (*)(double)>(IsPositive), 3.14);直接写EXPECT_PRED1(IsPositive, 5);会引发编译错误。
- 使用模板函数时需指定模板实参:
template <typename T> bool IsNegative(T x) { return x < 0; } ... EXPECT_PRED1(IsNegative<int>, -5); // 必须指定 IsNegative 的类型- 若模板有多个参数,需给谓词加括号,保证宏参数被正确解析:
ASSERT_PRED2((MyPredicate<int, int>), 5, 0);EXPECT_PRED_FORMAT1~5 / ASSERT_PRED_FORMAT1~5
EXPECT_PRED_FORMAT1(pred_formatter, val1) EXPECT_PRED_FORMAT2(pred_formatter, val1, val2) EXPECT_PRED_FORMAT3(pred_formatter, val1, val2, val3) EXPECT_PRED_FORMAT4(pred_formatter, val1, ..., val4) EXPECT_PRED_FORMAT5(pred_formatter, val1, ..., val5) // ASSERT_PRED_FORMAT1 ~ ASSERT_PRED_FORMAT5 同理验证谓词pred_formatter接收给定值作为实参时“成功”。pred_formatter是一个predicate-formatter,其签名为:
testing::AssertionResult PredicateFormatter(const char* expr1, const char* expr2, ... const char* exprn, T1 val1, T2 val2, ... Tn valn);其中val1..valn是谓词实参的值,expr1..exprn是对应表达式在源码中的原始文本。T1..Tn可以是值类型或引用类型——实参类型为T时,形参可声明为T或const T&。返回类型testing::AssertionResult的用法参见 Using a Function That Returns an AssertionResult。
示例:
// 返回 m 和 n 的最小素公因子;m、n 互素时返回 1。 int SmallestPrimeCommonDivisor(int m, int n) { ... } // m、n 除 1 之外无公因子时返回 true。 bool MutuallyPrime(int m, int n) { ... } // 断言两个整数互素的 predicate-formatter。 testing::AssertionResult AssertMutuallyPrime(const char* m_expr, const char* n_expr, int m, int n) { if (MutuallyPrime(m, n)) return testing::AssertionSuccess(); return testing::AssertionFailure() << m_expr << " and " << n_expr << " (" << m << " and " << n << ") are not mutually prime, " << "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n); } ... const int a = 3; const int b = 4; const int c = 10; ... EXPECT_PRED_FORMAT2(AssertMutuallyPrime, a, b); // 成功 EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c); // 失败最后一条断言失败时,predicate-formatter 生成的失败消息为:
b and c (4 and 10) are not mutually prime, as they have a common divisor 2这就是 predicate-formatter 相对普通谓词的价值:消息文本由你定制,可以把源码表达式与计算出的公因子一并输出。仓库还内置了现成工具:gtest.h 中的IsSubstring/IsNotSubstring就是专为EXPECT_PRED_FORMAT2设计的 predicate-formatter(NULL 仅当与自身比较时视为子串)。
Windows HRESULT 断言
这一类断言测试HRESULT的成功或失败,例如:
CComPtr<IShellDispatch2> shell; ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application")); CComVariant empty; ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty));生成的输出中会包含与返回的HRESULT码相关联的人类可读错误消息。
EXPECT_HRESULT_SUCCEEDED(expression)/ASSERT_HRESULT_SUCCEEDED(...):验证expression是成功HRESULT;EXPECT_HRESULT_FAILED(expression)/ASSERT_HRESULT_FAILED(...):验证expression是失败HRESULT。
Death 断言
这一类断言验证一段代码会导致进程终止,背景知识参见 Death Tests。
工作机制(文档原文要点):断言会启动一个新进程并在其中执行被测代码。具体方式取决于平台和变量::testing::GTEST_FLAG(death_test_style),它由命令行标志--gtest_death_test_style初始化:
- POSIX 系统:用
fork()(Linux 上为clone())派生子进程,随后:- 值为
"fast"时,death test 语句在子进程中立即执行; - 值为
"threadsafe"时,子进程像最初被调用一样重新执行单元测试二进制,但附加额外标志,使只有当前这一个 death test 被运行;
- 值为
- Windows:用
CreateProcess()API 派生子进程并重新执行二进制,只运行当前 death test——与 POSIX 的"threadsafe"模式类似; - 其他取值非法,会导致 death test 失败。该标志当前默认值为
"fast"。
如果 death test 语句正常跑完而没有导致进程死亡,子进程仍会终止,断言判为失败。
被测代码同样可以是复合语句:
EXPECT_DEATH({ int n = 5; DoSomething(&n); }, "Error on line .* of DoSomething()");EXPECT_DEATH / ASSERT_DEATH
EXPECT_DEATH(statement,matcher)
验证statement使进程以非零退出状态终止,且stderr输出匹配matcher。
matcher是面向const std::string&的 matcher,或一个正则表达式(语法见 Regular Expression Syntax)。注意:不带 matcher 的裸字符串s会被当作ContainsRegex(s),而不是Eq(s)——这是使用中最容易踩坑的一点。
例如,验证调用DoSomething(42)会让进程带着包含My error的错误消息死亡:
EXPECT_DEATH(DoSomething(42), "My error");EXPECT_DEATH_IF_SUPPORTED / ASSERT_DEATH_IF_SUPPORTED
EXPECT_DEATH_IF_SUPPORTED(statement,matcher)
如果平台支持 death test,行为与EXPECT_DEATH相同;否则不做任何验证。
EXPECT_DEBUG_DEATH / ASSERT_DEBUG_DEATH
EXPECT_DEBUG_DEATH(statement,matcher)
调试模式下行为与EXPECT_DEATH相同;非调试模式(定义了NDEBUG)下仅执行statement本身。
EXPECT_EXIT / ASSERT_EXIT
EXPECT_EXIT(statement,predicate,matcher)
验证statement导致进程以满足predicate的退出状态终止,且stderr输出匹配matcher。
predicate是接受int退出状态并返回bool的函数或仿函数。GoogleTest 提供了两个常见用例的谓词:
// 若程序以给定退出码正常退出则返回 true。 ::testing::ExitedWithCode(exit_code); // 若程序被给定信号杀死则返回 true。 // Windows 上不可用。 ::testing::KilledBySignal(signal_number);matcher的语义与EXPECT_DEATH相同:可以是const std::string&的 matcher,或正则表达式;裸字符串按ContainsRegex(s)处理而非Eq(s)。
例如,验证调用NormalExit()会在stderr打印包含Success的消息并以退出码 0 结束:
EXPECT_EXIT(NormalExit(), testing::ExitedWithCode(0), "Success");仓库中的 gtest-death-test.cc 实现了上述全部 death test 机制,配套的 googletest-death-test-test.cc 则覆盖了fast/threadsafe两种风格下的行为,可作为深入阅读的实现入口。
速查总结
| 场景 | 首选宏 |
|---|---|
| 布尔表达式 | EXPECT_TRUE/EXPECT_FALSE |
| 直接报失败(控制流驱动) | FAIL()(致命)/ADD_FAILURE()(非致命)/ADD_FAILURE_AT |
| 数值相等/不等/大小 | EXPECT_EQ、EXPECT_NE、EXPECT_LT/LE/GT/GE |
| C 字符串按内容比较 | EXPECT_STREQ/STRNE/STRCASEEQ/STRCASENE |
| 浮点近似 | EXPECT_FLOAT_EQ、EXPECT_DOUBLE_EQ(4 ULP)、EXPECT_NEAR(自定义绝对误差) |
| 抛/不抛异常 | EXPECT_THROW、EXPECT_ANY_THROW、EXPECT_NO_THROW |
| 多参数自定义谓词 | EXPECT_PRED1~5、EXPECT_PRED_FORMAT1~5 |
| Windows COM HRESULT | EXPECT_HRESULT_SUCCEEDED/EXPECT_HRESULT_FAILED |
| 进程终止 | EXPECT_DEATH、EXPECT_EXIT、EXPECT_DEBUG_DEATH、EXPECT_DEATH_IF_SUPPORTED |
| Matcher 风格断言 | EXPECT_THAT(搭配 Matchers Reference) |
选择EXPECT_还是ASSERT_的核心判据只有一条:失败后是否还需要继续执行当前函数的后续断言。前者非致命、继续运行;后者致命、立即离开当前函数,因此仅能用于void函数。
【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考