assert仅校验单表达式且在NDEBUG下失效,无法提供参数值快照与类型信息;真正可靠的测试宏需保留原始表达式字符串、捕获变量实际值与类型,并通过__FILE__和__LINE__生成唯一ID以支持持续集成。

为什么不能直接用 assert 做多参数测试
因为 assert 只接收单个表达式,失败时只打印该表达式文本,不带参数值快照;更关键的是它在 NDEBUG 下彻底消失,无法用于持续集成或发布构建中的验证逻辑。真正需要的是「运行时可观察、可定位、不被优化掉」的断言机制。
TEST 宏必须捕获参数值并生成唯一标识
宏展开时要保留原始参数字符串(比如 a + b == c),同时求值并记录实际值(a、b、c 的当前值)。否则报错时只能看到“表达式为假”,却不知道 a=3、b=0、c=4 这类上下文。
- 用
__LINE__和__FILE__生成唯一测试 ID,避免同文件多测试冲突 - 用逗号表达式 + lambda 捕获所有变量值:例如
([&](){ return a + b == c; }(), a, b, c)不行——这会丢掉表达式本身;正确做法是分别记录表达式字符串和各变量值 - 推荐结构:
TEST(a + b == c, a, b, c)→ 展开为一个匿名函数调用,内部用std::cout或日志输出表达式文本 + 各参数decltype和std::forward_as_tuple打包值
如何让失败信息包含类型和值(避免 std::cout 崩溃)
不是所有类型都支持 operator。直接 <code>std::cout 在遇到自定义类型且没重载流操作符时编译失败。必须做 SFINAE 或 C++20 <code>concepts 判断,但简易版可用 std::string_view + std::to_string 仅覆盖基本类型,其余 fallback 到 typeid(x).name()。
- 对
int、double、const char*、std::string提供特化输出 - 其他类型统一用
std::cout - 注意:不要试图在宏里用
constexpr if—— 宏展开发生在编译器解析前,C++20 概念无法介入
宏定义里为什么必须用 do { ... } while(0)
否则在 if (cond) TEST(...); else ... 场景下,宏展开后分号会吃掉 else,导致语法错误。这是 C/C++ 宏最经典也最容易忽略的陷阱。
立即学习“C++免费学习笔记(深入)”;
正确写法示例:
#define TEST(expr, ...) do { \
bool _test_result = (expr); \
if (!_test_result) { \
std::cerr << "TEST FAILED at " << __FILE__ << ":" << __LINE__ \
<< " [" #expr "] "; \
/* 输出 __VA_ARGS__ 中每个参数的值 */ \
std::cerr << "\n"; \
std::abort(); \
} \
} while(0)注意:这里 #expr 是字符串化,__VA_ARGS__ 需配合 GCC/Clang 的可变宏扩展,MSVC 要加 /Zc:preprocessor 支持。
真实项目中,这种宏只适合快速验证逻辑;复杂断言仍建议用 catch2 或 gtest——它们处理了线程安全、测试注册、输出格式化等细节。自己写的宏一旦开始支持期望值/实际值对比、浮点误差容忍、跳过标记,很快就会变成 mini 测试框架。


















