【发布时间】:2021-07-01 07:13:30
【问题描述】:
前言
我知道有几个可用的自动测试库。 请让我们忽略这个问题。
动机
实现一些库我厌倦了手动测试,所以我开始编写一个“自测”程序,从使用许多assert()s 的代码开始。
不幸的是,当assert() 失败时,屏幕上只显示有限的信息,我通常必须使用调试器检查核心转储以获取更多失败的详细信息。
所以我添加了一个宏,当断言失败时,它允许类似printf() 的输出(通过E() (for error) 宏实现);我将那个宏命名为VA()(用于详细断言):
#define VA(assert_cond, msg, ...) do { \
if ( !(assert_cond) ) E(msg, ##__VA_ARGS__); \
assert(assert_cond); \
} while (0)
使用它看起来像这样:
VA(FASTWORD(FASTWORD_BITS - 1) == 0, "%s: FASTWORD() failed", __func__);
由于自测程序使用了类似数组的数据结构,我也需要检查这些,所以我在测试之前输出了这些,即使所有测试都成功了,也会产生很多输出。
所以我发明了另一个宏,VFA()(verbose failed assertion),它使用这样的“lambda 参数”:
#define VFA(assert_cond, cmd, msg, ...) do { \
if ( !(assert_cond) ) { \
E(msg, ##__VA_ARGS__); \
cmd; \
} \
assert(assert_cond); \
} while (0)
在写作时,我想知道预处理器如何为这样的用例解析逗号:
VFA(fw[0] == out_fw0 && fw[1] == out_fw1,
dump_fastwords_range(fw, 4, pos, (pos + count) % FASTWORD_BITS),
"%s: __clear_fw_bits_up(%d, %d) failed", context, pos, count);
我的意思是条件可能是第一个参数,dump_fastwords_range(fw 可能是第二个,4 可能是第三个,依此类推...
但至少gcc 并非如此。
另一件事是宏中的cmd;:
我的第一个版本没有分号,所以我不得不写(看起来很丑):
VFA(fw[0] == out_fw0 && fw[1] == out_fw1,
dump_fastwords_range(fw, 4, pos, (pos + count) % FASTWORD_BITS);,
"%s: __clear_fw_bits_up(%d, %d) failed", context, pos, count);
好的,这是我的宏的另一个使用示例:
VFA(fw[0] == out_fw0 && fw[1] == out_fw1,
{
const unsigned first = pos >= count ?
pos - count : FASTWORD_BITS + pos - count + 1;
dump_fastwords_range(fw, 4, first, pos);
},
"%s: __clear_fw_bits_dn(%d, %d) failed", context, pos, count);
问题
我的问题是:
- 宏参数的解析是否可以跨编译器移植?
- 考虑到参数可能相当复杂(如上一个示例所示),使用
cmd会不会造成任何麻烦?
【问题讨论】:
-
C 允许相当长的标识符。与其命名像
VA这样完全神秘的东西,然后想出它代表什么的解释,只需将宏命名为verbose_assert。同样,不要命名为“Efor error”,将其命名为print_err或其他名称。对于其他 C 程序员来说,没有什么比强迫他们学习一种秘密的宏语言来阅读你的代码更烦人的了。 -
一个常见的众所周知的技巧是编写像
assert(("Failed to do stuff",cond));这样的断言,其中,是逗号运算符。然后大多数编译器会给出一个错误,例如:“断言在第 x 行失败,表达式:(“无法做事”,0)”。这很不言自明。 -
@Lundin 总的来说,我同意你关于简短和神秘名称的说法,但自检模块不适合最终用户查看。在输入数百个测试时,我通常会打错那些冗长的名称。
-
“我不喜欢/管理打字”不是设计程序时使用的有效理由。任何体面的 IDE 都具有代码完成功能。而且总是有复制/粘贴。除此之外,程序员的职业就是要打字很多。那些不喜欢它的人可能最好从事其他职业。