GoogleTest 断言宏全面参考:在 yaml-cpp 测试中系统掌握 EXPECT_ 与 ASSERT_ 断言体系
【免费下载链接】yaml-cppA YAML parser and emitter in C++项目地址: https://gitcode.com/GitHub_Trending/ya/yaml-cpp
本文是 GoogleTest 1.16.0 断言宏(Assertions Reference)的完整技术参考,以 test/googletest-1.16.0/docs/reference/assertions.md 为骨架,结合 yaml-cpp 仓库内真实测试代码进行印证与纵深讲解。读完本文,你将掌握EXPECT_/ASSERT_两大族系的全部断言宏:从布尔、数值、字符串、浮点、异常、谓词、HRESULT 到死亡断言,并能在 yaml-cpp 这类 C++ 库的单元测试中熟练选用、写出可读且诊断信息丰富的断言。
断言体系总览:EXPECT_ 与 ASSERT_ 两大族系
使用所有断言宏前,需要在测试文件中包含头文件:
#include <gtest/gtest.h>GoogleTest 的大多数断言宏都以"成对"形式提供:一个EXPECT_变体和一个ASSERT_变体,二者在失败时的行为截然不同:
EXPECT_*(非致命失败):断言失败时记录一条失败信息,但允许当前函数继续执行。适合"一个测试内有多处可独立校验的断言"的场景,失败后仍能收集后续断言的更多信息。ASSERT_*(致命失败):断言失败时立即中止当前函数的执行。适合"后续代码依赖前置条件成立"的场景,例如指针非空、对象已正确初始化后再访问其成员。
以 yaml-cpp 的 test/integration/emitter_test.cpp 为例,ASSERT_FALSE(out.good())失败即直接返回,因为后续输出out.c_str()只有在 emitter 状态异常时才需要被打印,此时继续执行没有意义:
ASSERT_FALSE(out.good()) << "Emitter cleanly produced: " << out.c_str();向断言流式传输自定义失败信息
所有断言宏都支持通过<<运算符向宏中追加自定义失败信息:
EXPECT_TRUE(my_condition) << "My condition is not true";任何能够流式输出到ostream的对象都可以流式传入断言宏——特别是 C 字符串(const char*)和std::string对象。如果向断言中流式传入宽字符串(wchar_t*、WindowsUNICODE模式下的TCHAR*或std::wstring),打印时会自动转换为 UTF-8 编码。yaml-cpp 的 emitter 测试就大量使用这种技巧,例如<< "Emitter cleanly produced: " << out.c_str()在断言失败时把 emitter 的完整输出一并呈现,极大提升排错效率。
显式成功与失败断言
这一类断言不校验任何值或表达式,而是直接产生成功或失败结果。它们适用于"由控制流而非布尔表达式决定测试成败"的场景,例如switch分支中无法落入任何已知分支时必须报告失败:
switch(expression) { case 1: ... some checks ... case 2: ... some other checks ... default: FAIL() << "We shouldn't get here."; }SUCCEED()
SUCCEED()直接产生一条成功记录。注意:它并不会让整个测试"自动成功"——一个测试只有在执行过程中没有任何断言失败时才被视为成功。SUCCEED目前纯粹是文档性质的标记,不产生任何用户可见输出(未来 GoogleTest 可能会在输出中加入SUCCEED消息)。
FAIL()
FAIL()直接产生一个致命失败,立即从当前函数返回。因为它会中止当前函数,所以只能在返回void的函数中使用。关于断言的放置位置限制,参见 test/googletest-1.16.0/docs/advanced.md#assertion-placement(例如不要在带返回值的辅助函数中直接FAIL(),除非该函数返回void)。
ADD_FAILURE()
ADD_FAILURE()直接产生一个非致命失败,当前函数继续运行。与FAIL()形成对照:它相当于"人工制造一个可恢复的断言失败"。
ADD_FAILURE_AT(file_path, line_number)
ADD_FAILURE_AT(file_path,line_number)在指定的文件与行号处产生一个非致命失败。这在测试框架包装层(如自定义的测试辅助函数中,希望把失败定位到调用方源码位置而非辅助函数内部)非常有用。
广义断言:EXPECT_THAT 与匹配器
EXPECT_THAT(value, matcher) ASSERT_THAT(value, matcher)EXPECT_THAT/ASSERT_THAT校验value是否匹配matcher(匹配器)。匹配器体系来自 test/googletest-1.16.0/docs/reference/matchers.md,使用前需要包含:
#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)));匹配器让断言读起来像自然语言,并生成信息丰富的失败消息。例如value1的断言失败时,输出类似:
Value of: value1 Actual: "Hi, world!" Expected: starts with "Hello"GoogleTest 内置了丰富的匹配器库(见 test/googletest-1.16.0/docs/reference/matchers.md),也支持自定义匹配器(见 test/googletest-1.16.0/docs/gmock_cook_book.md)。EXPECT_THAT因此是一种强大且可扩展的断言形式。该断言的思路借鉴自 Joe Walnes 的 Hamcrest 项目,后者为 JUnit 增加了assertThat()。
yaml-cpp 中的真实运用:在 test/integration/clone_node_test.cpp 中,测试节点克隆后 map 的键位置信息时,用testing::UnorderedElementsAre校验"键的 Mark 集合与预期完全一致、且不依赖键的遍历顺序":
EXPECT_THAT(key_marks, testing::UnorderedElementsAre( ComparableMark(1, 1, 0), ComparableMark(15, 2, 0), ComparableMark(35, 3, 0), ComparableMark(48, 4, 0)));这正是EXPECT_THAT相对手写循环校验的优势:集合比较的语义(无序、元素逐一匹配)直接由匹配器表达,代码意图一目了然。
布尔条件断言
EXPECT_TRUE(condition) / ASSERT_TRUE(condition) EXPECT_FALSE(condition) / ASSERT_FALSE(condition)EXPECT_TRUE校验condition为真,EXPECT_FALSE校验condition为假。
yaml-cpp 中的真实运用:布尔断言常配合节点的类型与状态查询。例如 test/integration/load_node_test.cpp 用EXPECT_TRUE(node.IsNull())校验~被解析为空节点,test/integration/clone_node_test.cpp 则用致命版本的ASSERT_TRUE(sequence_key)确保"确实存在序列键"这一前置条件成立后,才继续对sequence_key[0]、sequence_key[1]等下标解引用——若前置不成立就继续访问,会引发空指针/无效节点解引用,因此必须用ASSERT_而非EXPECT_:
ASSERT_TRUE(sequence_key); EXPECT_EQ(sequence_key[0].Mark(), ComparableMark(49, 4, 1)); EXPECT_EQ(sequence_key[1].Mark(), ComparableMark(52, 4, 4));这是ASSERT_致命失败语义的教科书级用例:后续断言依赖前置条件。
二元比较断言
下列断言比较两个值,参数必须能够被对应比较运算符比较,否则会产生编译错误:
EXPECT_EQ(val1, val2) / ASSERT_EQ(val1, val2) // val1 == val2 EXPECT_NE(val1, val2) / ASSERT_NE(val1, val2) // val1 != val2 EXPECT_LT(val1, val2) / ASSERT_LT(val1, val2) // val1 < val2 EXPECT_LE(val1, val2) / ASSERT_LE(val1, val2) // val1 <= val2 EXPECT_GT(val1, val2) / ASSERT_GT(val1, val2) // val1 > val2 EXPECT_GE(val1, val2) / ASSERT_GE(val1, val2) // val1 >= val2打印与求值语义
- 若参数支持
<<运算符,断言失败时会调用它打印该参数;否则 GoogleTest 会尽力以最佳方式打印(参见 test/googletest-1.16.0/docs/advanced.md#teaching-googletest-how-to-print-your-values)。 - 参数总是恰好求值一次,因此参数带副作用是允许的;但参数的求值顺序未定义,程序不应依赖任何特定的求值顺序。
- 这些断言同时支持窄字符串对象和宽字符串对象(
std::string与std::wstring)。 - 比较浮点数请使用下文 浮点比较断言,避免舍入误差导致的假失败。
指针与 NULL 的特殊处理
EXPECT_EQ/EXPECT_NE对指针做的是指针相等性判断。如果用于两个 C 字符串,它比较的是二者是否位于同一内存位置,而不是内容是否相等——比较 C 字符串内容应使用EXPECT_STREQ。比较指针与NULL时,应写EXPECT_EQ(ptr, nullptr),而不是EXPECT_EQ(ptr, NULL)。
yaml-cpp 中的真实运用:二元比较断言是 yaml-cpp 测试的主力。仅 test/integration/load_node_test.cpp 一个文件中就出现了 123 处EXPECT_断言,其中绝大多数是EXPECT_EQ。例如 test/integration/load_node_test.cpp 校验节点访问的默认值回退逻辑:
TEST(LoadNodeTest, FallbackValues) { YAML::Node node = YAML::Load("foo: bar"); EXPECT_EQ("bar", node["foo"].as<std::string>()); EXPECT_EQ("bar", node["foo"].as<std::string>("hello")); EXPECT_EQ("hello", node["baz"].as<std::string>("hello")); EXPECT_EQ(2, node["x"].as<int>()); EXPECT_EQ(2, node["x"].as<int>(5)); EXPECT_EQ(5, node["y"].as<int>(5)); }注意 yaml-cpp 测试中统一采用EXPECT_EQ(期望值, 实际值)的参数顺序,这与 GoogleTest 失败消息中"Expected/Actual"的显示顺序一致,能让失败信息更易读。在 test/integration/node_spec_test.cpp 中EXPECT_断言更是多达 523 处,覆盖节点类型、标记(Mark)、迭代等方方面面;test/fptostring_test.cpp 则用EXPECT_EQ("34.34", FpToString(34.34))校验浮点转字符串的精确输出。
C 字符串比较断言
下列断言比较两个C 字符串(const char*)。要比较两个std::string对象,应改用EXPECT_EQ/EXPECT_NE。这些断言同样接受宽 C 字符串(wchar_t*);如果两个宽字符串比较失败,其值会以 UTF-8 窄字符串形式打印。
EXPECT_STREQ(str1, str2) / ASSERT_STREQ(str1, str2) // 内容相同 EXPECT_STRNE(str1, str2) / ASSERT_STRNE(str1, str2) // 内容不同 EXPECT_STRCASEEQ(str1, str2) / ASSERT_STRCASEEQ(...) // 内容相同(忽略大小写) EXPECT_STRCASENE(str1, str2) / ASSERT_STRCASENE(...) // 内容不同(忽略大小写)EXPECT_STREQ校验两个 C 字符串内容相同,EXPECT_STRNE校验内容不同;EXPECT_STRCASEEQ/EXPECT_STRCASENE则在比较时忽略大小写。把 C 字符串与NULL比较时,使用EXPECT_EQ(c_string, nullptr)或EXPECT_NE(c_string, nullptr)。
实战提示:yaml-cpp 测试中 emitter 输出、标签解析等场景若涉及裸字符指针,应优先使用EXPECT_STREQ族而非EXPECT_EQ,否则会退化为指针地址比较。
浮点比较断言
由于舍入误差,两个浮点值几乎不可能精确相等,因此EXPECT_EQ不适合浮点比较。一般而言,有意义的浮点比较需要用户仔细选择误差界。GoogleTest 还提供了基于ULP(Units in the Last Place,最后一位单位)的默认误差界断言。
EXPECT_FLOAT_EQ(val1, val2) / ASSERT_FLOAT_EQ(val1, val2) EXPECT_DOUBLE_EQ(val1, val2) / ASSERT_DOUBLE_EQ(val1, val2)EXPECT_FLOAT_EQ:校验两个float值近似相等,允许相差4 个 ULP。无穷大与最大有限 float 值被视为相隔 1 个 ULP。EXPECT_DOUBLE_EQ:校验两个double值近似相等,同样允许相差 4 个 ULP。
EXPECT_NEAR:绝对误差界比较
EXPECT_NEAR(val1, val2, abs_error) / ASSERT_NEAR(val1, val2, abs_error)EXPECT_NEAR校验val1与val2之差不超过绝对误差界abs_error。边界语义:
- 若两个值都是同号无穷大,差视为 0;
- 否则若任一方是无穷大,差视为无穷大;
- 所有非 NaN 的值(含无穷大)都不被认为会超过
abs_error为无穷大的误差界。
yaml-cpp 中的真实运用:test/integration/load_node_test.cpp 的数值转换测试中,先以EXPECT_EQ(1.5f, Load("1.5").as<float>())校验整数值表示的小数,再用EXPECT_NE校验 NaN 与自身不相等,用EXPECT_EQ与std::numeric_limits<float>::infinity()校验正负无穷的解析:
EXPECT_EQ(1.5f, Load("1.5").as<float>()); EXPECT_EQ(1.5, Load("1.5").as<double>()); EXPECT_NE(Load(".nan").as<float>(), Load(".nan").as<float>()); EXPECT_EQ(std::numeric_limits<float>::infinity(), Load(".inf").as<float>()); EXPECT_EQ(-std::numeric_limits<float>::infinity(), Load("-.inf").as<float>());这里的EXPECT_NE正是利用了 NaN 不等于自身的 IEEE 754 语义——这也是"用断言表达领域语义"的范例。凡是涉及浮点解析、发射后回读的测试,都应该优先考虑EXPECT_FLOAT_EQ/EXPECT_DOUBLE_EQ/EXPECT_NEAR以规避舍入抖动。
异常断言
下列断言校验一段代码抛出或不抛出异常,使用前提是构建环境启用了异常(yaml-cpp 的 CMake 构建默认满足该前提)。被测代码可以是复合语句块:
EXPECT_NO_THROW({ int n = 5; DoSomething(&n); });EXPECT_THROW(statement, exception_type) / ASSERT_THROW(statement, exception_type) EXPECT_ANY_THROW(statement) / ASSERT_ANY_THROW(statement) EXPECT_NO_THROW(statement) / ASSERT_NO_THROW(statement)EXPECT_THROW:校验statement抛出类型为exception_type的异常;EXPECT_ANY_THROW:校验statement抛出任意类型的异常;EXPECT_NO_THROW:校验statement不抛出任何异常。
yaml-cpp 中的真实运用:异常断言在 yaml-cpp 中扮演核心角色,因为库本身用异常报告解析错误与类型转换错误。例如 test/integration/load_node_test.cpp 校验"把非整数文本转成int必须抛出TypedBadConversion<int>"、"越界的int8_t/uint8_t转换必须抛出对应类型转换异常":
EXPECT_THROW(Load("1.5").as<int>(), TypedBadConversion<int>); EXPECT_THROW(Load("128").as<int8_t>(), TypedBadConversion<signed char>); EXPECT_THROW(Load("256").as<uint8_t>(), TypedBadConversion<unsigned char>);同文件 test/integration/load_node_test.cpp 还用大量EXPECT_THROW(Load(...), ParserException)断言非法 YAML 输入(未闭合引号、非法流式语法"foo",bar、空序列流,等)必须被解析器拒绝。这验证了EXPECT_THROW是"负向测试"(测试错误路径)的标准工具——断言异常类型比简单判断"报错了"更精确。
谓词断言
谓词断言允许校验更复杂的谓词,同时打印比单独使用EXPECT_TRUE更清晰的失败消息。
EXPECT_PRED*:直接调用谓词
EXPECT_PRED1(pred, val1) ASSERT_PRED1(pred, val1) EXPECT_PRED2(pred, val1, val2) ASSERT_PRED2(pred, val1, val2) EXPECT_PRED3(pred, val1, val2, val3) ASSERT_PRED3(pred, val1, val2, val3) EXPECT_PRED4(pred, val1, val2, val3, val4) ASSERT_PRED4(pred, val1, val2, val3, val4) EXPECT_PRED5(pred, val1, val2, val3, val4, val5) ASSERT_PRED5(pred, val1, val2, val3, val4, val5)pred是接受与宏值参数数量相同参数的函数或仿函数;对给定参数返回true则断言成功,否则失败。失败时会打印每个参数的值,参数总是恰好求值一次。例如:
// Returns true if m and n have no common divisors except 1. bool MutuallyPrime(int m, int n) { ... } const int a = 3; const int b = 4; const int c = 10; EXPECT_PRED2(MutuallyPrime, a, b); // Succeeds EXPECT_PRED2(MutuallyPrime, b, c); // Fails第二个断言失败时的消息会自动带上每个参数的值:
MutuallyPrime(b, c) is false, where b is 4 c is 10使用限制与解法:如果谓词是重载函数或函数模板,宏可能无法确定该用哪个版本,需要显式指定函数类型:
// 重载的 IsPositive():int 与 double 各一个版本 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); // Must specify type for IsNegative若模板有多个参数,用括号把谓词包起来,使宏参数解析正确:
ASSERT_PRED2((MyPredicate<int, int>), 5, 0);EXPECT_PRED_FORMAT*:自定义失败消息格式
EXPECT_PRED_FORMAT1(pred_formatter, val1) EXPECT_PRED_FORMAT2(pred_formatter, val1, val2) ... EXPECT_PRED_FORMAT5(pred_formatter, val1, ..., val5) ASSERT_PRED_FORMAT1(pred_formatter, val1) ... ASSERT_PRED_FORMAT5(pred_formatter, val1, ..., val5)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的使用方式参见 test/googletest-1.16.0/docs/advanced.md#using-a-function-that-returns-an-assertionresult。
完整示例——自定义"两整数互质"断言及其失败消息:
// Returns the smallest prime common divisor of m and n, // or 1 when m and n are mutually prime. int SmallestPrimeCommonDivisor(int m, int n) { ... } bool MutuallyPrime(int m, int n) { ... } // A predicate-formatter for asserting that two integers are mutually prime. 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); // Succeeds EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c); // Fails最后一个断言失败时产生如下消息(自动代入表达式文本b、c与实参值 4、10):
b and c (4 and 10) are not mutually prime, as they have a common divisor 2EXPECT_PRED_FORMAT*的价值在于:失败消息里既能看到源码表达式(b、c),又能看到实际值(4、10),还能附加领域化的诊断("有一个公共因子 2"),比EXPECT_TRUE(MutuallyPrime(b, c))的失败输出可读性高一个量级。GoogleTest 自身的 test/googletest-1.16.0/googlemock/test/gmock-cardinalities_test.cc 也大量使用EXPECT_PRED_FORMAT2(IsSubstring, ...)来校验 mock 调用次数文本,可作参考。
Windows HRESULT 断言
下列断言测试HRESULT的成功或失败状态,主要用于 Windows 平台 COM 编程,生成输出会包含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));EXPECT_HRESULT_SUCCEEDED(expression) / ASSERT_HRESULT_SUCCEEDED(expression) EXPECT_HRESULT_FAILED(expression) / ASSERT_HRESULT_FAILED(expression)EXPECT_HRESULT_SUCCEEDED:校验expression是成功状态的HRESULT;EXPECT_HRESULT_FAILED:校验expression是失败状态的HRESULT。
死亡断言
死亡断言校验一段代码导致进程终止(相关背景参见 test/googletest-1.16.0/docs/advanced.md#death-tests)。
执行机制
死亡断言会新开一个进程并在该进程中执行被测代码,具体机制取决于平台与变量::testing::GTEST_FLAG(death_test_style)(由命令行参数--gtest_death_test_style初始化):
- POSIX 系统:使用
fork()(Linux 上为clone())生成子进程,随后:- 若变量值为
"fast":立即执行死亡测试语句; - 若变量值为
"threadsafe":子进程像最初被调用那样重新执行单元测试二进制,但附加额外标志,使仅运行当前这一个死亡测试。
- 若变量值为
- Windows:使用
CreateProcess()API 生成子进程并重新执行二进制,仅运行当前这一个死亡测试——与 POSIX 的"threadsafe"模式类似。
其他取值非法,会导致死亡测试失败。目前该标志的默认值为"fast"。
如果死亡测试语句运行到结束都没有"死亡",子进程仍会终止,此时断言失败。被测代码同样可以是复合语句块:
EXPECT_DEATH({ int n = 5; DoSomething(&n); }, "Error on line .* of DoSomething()");EXPECT_DEATH
EXPECT_DEATH(statement, matcher) / ASSERT_DEATH(statement, matcher)校验statement导致进程以非零退出状态终止,且产生的stderr 输出匹配matcher。matcher可以是对const std::string&的匹配器,也可以是正则表达式(语法见 test/googletest-1.16.0/docs/advanced.md#regular-expression-syntax)——裸字符串s(不带匹配器)会被当作ContainsRegex(s)处理,而不是Eq(s)。例如:
EXPECT_DEATH(DoSomething(42), "My error");该校验调用DoSomething(42)导致进程终止,且错误消息包含文本My error。
EXPECT_DEATH_IF_SUPPORTED
EXPECT_DEATH_IF_SUPPORTED(statement, matcher) / ASSERT_DEATH_IF_SUPPORTED(statement, matcher)若平台支持死亡测试,行为与EXPECT_DEATH完全一致;否则不校验任何内容(直接通过)。GoogleTest 自身的 test/googletest-1.16.0/googlemock/test/gmock-actions_test.cc 中大量使用EXPECT_DEATH_IF_SUPPORTED来测试"对非法行为应当崩溃"的代码路径,同时保持跨平台可移植。
EXPECT_DEBUG_DEATH
EXPECT_DEBUG_DEATH(statement, matcher) / ASSERT_DEBUG_DEATH(statement, matcher)在调试模式下行为与EXPECT_DEATH相同;在非调试模式(即定义了NDEBUG)下,仅执行statement,不做任何断言。适合测试"仅在assert等调试检查下才会触发的崩溃"。
EXPECT_EXIT
EXPECT_EXIT(statement, predicate, matcher) / ASSERT_EXIT(statement, predicate, matcher)校验statement导致进程终止,且退出状态满足predicate、stderr 输出匹配matcher。predicate是接受int退出状态并返回bool的函数或仿函数。GoogleTest 提供了两个现成谓词:
// Returns true if the program exited normally with the given exit status code. ::testing::ExitedWithCode(exit_code); // Returns true if the program was killed by the given signal. // Not available on Windows. ::testing::KilledBySignal(signal_number);matcher的语义与EXPECT_DEATH相同(裸字符串按ContainsRegex处理)。例如,校验NormalExit()打印包含Success的 stderr 消息并以退出码 0 正常退出:
EXPECT_EXIT(NormalExit(), testing::ExitedWithCode(0), "Success");在 yaml-cpp 中实际编排断言:测试工程视角
yaml-cpp 仓库把 GoogleTest 1.16.0 作为测试框架内嵌于 test/googletest-1.16.0,测试源码则分布在test目录下,通过 test/CMakeLists.txt 组织构建(测试程序统一由 test/main.cpp 提供入口)。断言在其中的分布可以直观看到断言体系的选型规律:
- test/integration/load_node_test.cpp:约 123 处
EXPECT_,以EXPECT_EQ(解析值与默认值回退)、EXPECT_THROW(非法输入必须抛ParserException/TypedBadConversion)为绝对主力; - test/integration/node_spec_test.cpp:约 523 处
EXPECT_,覆盖节点 API 的规格化行为; - test/integration/clone_node_test.cpp:综合使用
EXPECT_THAT(无序集合匹配)、ASSERT_TRUE(前置条件)与EXPECT_EQ(逐项校验),并在SCOPED_TRACE("original node")包裹下对"克隆前后"两轮校验复用同一段检查代码; - test/fptostring_test.cpp:用
EXPECT_EQ对浮点转字符串的文本输出做逐字节精确断言。
这套分布本身就是一个"断言选型最佳实践"的活教材:数值与状态用EXPECT_EQ族、错误路径用EXPECT_THROW、前置条件用ASSERT_族、复杂集合语义用EXPECT_THAT+ 匹配器。当你在 yaml-cpp 或任何 C++ 项目上编写测试时,可以对照本文的断言分类,结合断言失败时消息的可读性、以及后续代码对前置条件的依赖,选择最贴切的断言宏。
快速选型速查
| 验证目标 | 推荐断言 | 说明 |
|---|---|---|
| 布尔条件 | EXPECT_TRUE/EXPECT_FALSE | 简单真假判断 |
| 数值/对象相等 | EXPECT_EQ/EXPECT_NE | 参数恰好求值一次;指针按地址比较 |
| 大小关系 | EXPECT_LT/LE/GT/GE | 需满足对应比较运算符 |
| C 字符串内容 | EXPECT_STREQ/EXPECT_STRNE | 按值比较,勿用EXPECT_EQ |
| 忽略大小写比较 | EXPECT_STRCASEEQ/EXPECT_STRCASENE | 仅限 C 字符串 |
| float 近似相等 | EXPECT_FLOAT_EQ | 默认 4 ULP 误差界 |
| double 近似相等 | EXPECT_DOUBLE_EQ | 默认 4 ULP 误差界 |
| 绝对误差界 | EXPECT_NEAR | 需显式给出abs_error |
| 抛出指定异常 | EXPECT_THROW | yaml-cpp 解析错误测试的主力 |
| 抛出任意异常/不抛 | EXPECT_ANY_THROW/EXPECT_NO_THROW | 支持复合语句块 |
| 复杂谓词 | EXPECT_PRED1..5 | 失败时自动打印各参数值 |
| 自定义失败消息 | EXPECT_PRED_FORMAT1..5 | 返回testing::AssertionResult |
| 匹配器语义 | EXPECT_THAT/ASSERT_THAT | 需包含<gmock/gmock.h>,可扩展 |
| 进程终止 | EXPECT_DEATH/EXPECT_EXIT | 校验 stderr 与退出状态 |
| Windows HRESULT | EXPECT_HRESULT_SUCCEEDED/FAILED | 输出人类可读错误消息 |
结语
GoogleTest 的断言体系在设计上遵循两条主线:EXPECT_非致命与ASSERT_致命的分工,以及失败消息必须自解释(流式<<、参数值自动打印、EXPECT_THAT的自然语言语义、EXPECT_PRED_FORMAT的完全定制)。yaml-cpp 的数千处测试断言表明,一个成熟 C++ 库的测试几乎会用到断言体系的每个分支——从EXPECT_EQ校验解析结果,到EXPECT_THROW锁定错误路径,再到EXPECT_THAT表达集合语义。理解并善用这套断言宏,是写出可信、可维护、排错高效的单元测试的第一步。
【免费下载链接】yaml-cppA YAML parser and emitter in C++项目地址: https://gitcode.com/GitHub_Trending/ya/yaml-cpp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考