直接用 assert 不够用,因其遇失败即退出、无计数分组、缺上下文信息;宏可注入行号文件名、统一管理执行流,实现可定位、可控、可读的测试。

为什么直接用 assert 不够用?
写测试时只靠 assert 会卡在第一个失败就退出,看不到后续所有用例结果;而且没计数、没分组、没失败详情——比如你改了个 vector::size() 的实现,跑完 12 个测试只看到 Assertion failed: size() == 5,但不知道是第几个 case、输入是什么、期望值多少。
宏能帮你把测试逻辑“包裹”起来,在编译期注入行号、文件名,并统一管理执行流。核心不是炫技,而是让失败信息可定位、执行可控、输出可读。
TEST 宏怎么定义才不踩坑?
最简可用的 TEST 需满足三点:独立作用域(避免变量重名)、自动注册(不用手动调)、失败时打印上下文。常见错误是直接用 {} 包裹语句块——这会导致 return 无法跳出当前函数,或者变量泄漏到外层。
- 用
do { ... } while(0)封装,既保证作用域隔离,又支持末尾加分号 - 用静态局部变量 + 函数指针数组模拟注册表,避免全局初始化顺序问题
- 每个测试用例生成唯一标识符,靠
__LINE__和__FILE__拼接,防止同文件多 test 冲突
示例:
立即学习“C++免费学习笔记(深入)”;
#define CONCAT_(a, b) a##b
#define CONCAT(a, b) CONCAT_(a, b)
#define TEST(name) \
static void CONCAT(test_, name)(); \
static struct { \
bool run() { CONCAT(test_, name)(); return true; } \
int dummy; \
} CONCAT(test_reg_, __LINE__) = {}; \
static void CONCAT(test_, name)()
如何让 EXPECT_EQ 报错时带具体值?
裸用 assert(a == b) 只报真假,不报 a=3, b=5。宏必须把表达式原样传入并展开,否则会丢失原始文本信息。
- 参数必须用
#x字符串化,再配合std::cout或fprintf输出原始表达式和求值结果 - 不能提前计算
a和b,否则报错时看不到原始变量名,比如EXPECT_EQ(x + y, z * 2)要显示 “x + y == 5, z * 2 == 4” - 用逗号表达式实现“先记录、再判断”,例如:
(auto _a = (a), _b = (b), _a == _b ? true : (std::cerr
这样写出来的 EXPECT_EQ(1 + 1, 3) 就能输出:1 + 1==2, 3==3(实际会标出差异)。
运行时怎么收集并批量执行测试?
没有 main 入口或注册机制,宏定义再漂亮也白搭。C++ 没有反射,得靠“副作用”触发注册——最稳的方式是利用全局对象构造函数。
- 定义一个全局
struct TestRunner,构造函数里遍历所有注册的测试函数指针 - 测试函数注册靠静态变量初始化,确保在
main前完成(注意:不同编译单元间顺序未定义,所以最好单文件测试或用init_priority) - 失败后别直接
exit(),先记下失败数,等全部跑完再返回非零码,方便 CI 判断
关键点:所有测试函数名要一致(如都叫 run()),靠结构体成员函数统一调用,避免函数指针类型不匹配。
宏的本质是文本替换,它不会帮你做类型推导、不会捕获异常、也不处理浮点误差。真要覆盖边界场景,得配合 EXPECT_FLOAT_EQ 这类专用宏,而它们背后还是靠宏展开时保留原始表达式——这点最容易被忽略:一旦用了 auto x = a; 再比较,就再也看不出 a 是什么了。


















