news 2026/9/17 11:14:50

GoogleTest 断言宏全面参考:在 yaml-cpp 测试中系统掌握 EXPECT_ 与 ASSERT_ 断言体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GoogleTest 断言宏全面参考:在 yaml-cpp 测试中系统掌握 EXPECT_ 与 ASSERT_ 断言体系

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::stringstd::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校验val1val2之差不超过绝对误差界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_EQstd::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,可声明为Tconst 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

最后一个断言失败时产生如下消息(自动代入表达式文本bc与实参值 4、10):

b and c (4 and 10) are not mutually prime, as they have a common divisor 2

EXPECT_PRED_FORMAT*的价值在于:失败消息里既能看到源码表达式bc),又能看到实际值(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 输出匹配matchermatcher可以是对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 输出匹配matcherpredicate是接受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_THROWyaml-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 HRESULTEXPECT_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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 11:14:29

Win11下JDK1.8与JDK17双环境管理实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 11:14:25

YOLOv11n优化在通航飞机蒙皮损伤检测中的应用探索

简介&#xff1a;这是一份围绕通航飞机蒙皮表面损伤检测的YOLOv11n算法优化研究文档&#xff0c;面向计算机视觉、目标检测及航空安全领域的研究者与学习者&#xff0c;旨在解决传统人工检测效率低、微小损伤识别难等问题。文档系统梳理了从算法原理、优化策略到实验验证的完整…

作者头像 李华
网站建设 2026/9/17 11:13:53

STM32F407ZGT6深度指南:封装、时钟、DMA与工业项目避坑

STM32F407ZGT6 这颗芯片&#xff0c;在嵌入式圈子里属于那种"不用多介绍&#xff0c;用过的人自然懂"的存在。144 脚 LQFP 封装、Cortex-M4 内核带 FPU、168MHz 主频、1MB Flash 加 192KB SRAM&#xff0c;再加上以太网 MAC、USB OTG、CAN、SDIO、DCMI 一整套外设&am…

作者头像 李华
网站建设 2026/9/17 11:13:48

Spacedesk实测:用虚拟显示器和旧手机免费组第二屏

干我们这行的&#xff0c;办公桌上最缺的从来不是咖啡&#xff0c;是屏幕。前阵子换回一台14寸笔记本出差&#xff0c;看代码、对需求文档、回消息三件事挤在一块屏上&#xff0c;来回切换窗口切到人暴躁。后来翻到Spacedesk这个项目&#xff0c;第一反应是&#xff1a;这玩意儿…

作者头像 李华
网站建设 2026/9/17 11:13:28

Grafana对接Easysearch搭建数据可视化大屏全流程实战

做运维和数据的同学应该都有这种感觉&#xff1a;日志和指标都堆在那边&#xff0c;业务方天天问“现在系统到底什么状态”“订单量涨了还是跌了”&#xff0c;你光靠命令行敲几个curl看结果&#xff0c;解释半天也讲不清楚。后面我把内部日志和业务索引统一收到 Easysearch 里…

作者头像 李华