是否可以对嵌入式软件的 C 宏进行单元测试?

Is it possible to unit test C macros for embedded software?

我是如何为嵌入式 SW 测试一些特定的 C 宏的。

例如,如果我有以下宏:

/*
Set a pin as an input
port  (B,C, D or E) 
pin  - pin to set (0-7)
*/
#define _SET_INPUT_PIN(port,pin) DDR ## port &= ~(1<<pin)
#define SET_INPUT_PIN(...) _SET_INPUT_PIN(__VA_ARGS__)

我想用两个端口测试它,一个存在 (LED_OK) 另一个不存在 (LED_FAIL):

#define LED_OK   D,3
#define LED_FAIL D,8

当我尝试测试它时,LED_OK 和 LED_FAIL 都可以正常工作,但某种 warning/fail 应该提醒 LED_FAIL 不存在,因为 PORTD 有仅定义引脚 0 到 7。

所以,当我通过 LED 时,我如何检查“pin”是否在范围内?

不,这不可能在编译时完成。你不能在宏替换中使用 say,a #if

从技术上讲,您可以将任何内容传递给 _SET_INPUT_PIN 的两个参数。它们只是在宏定义中出现的任何地方被预处理器盲目地替换。您可以将常量传递给它们,但它们也可以是变量。如果它是一个变量,那么它在编译时没有意义,因为变量只存在于 运行 时间。所以没有办法在编译时实现这个检查。您当然可以实施 运行 时间检查。

例如,

/*
Set a pin as an input
port  (B,C, D or E) 
pin  - pin to set (0-7)
*/
#define _SET_INPUT_PIN(port,pin) \do \
{ \
    if (pin < 8) \
        DDR ## port &= ~(1<<pin); \
    else \
        fail();\
}while(0)

这太隐晦了。总的来说,我强烈建议 不要 隐藏超级琐碎的东西,比如 setting/clearing 抽象层后面的引脚。

你应该这样写:

#define LED_DDR  DDRD
#define LED_PORT PORTD
#define LED_PIN  (1u << 3)

LED_DDR  |= LED_PIN;  // set pin to output
LED_PORT |= LED_PIN;  // set pin to 1
LED_PORT &= ~LED_PIN; // set pin to 0
LED_PORT &= (uint8_t)~LED_PIN; // set pin to 0 with pedantic type safety (MISRA-C etc)
LED_PORT ^= LED_PIN;  // toggle pin

您真的无法编写更清晰的代码,因此超出此范围的任何抽象层都注定会失败。它不会增加清晰度,但可能会增加错误。从 SO/EE 站点上人们尝试这样做的数千次粗略尝试可以看出。或者从硅供应商的过时软件库中可以看出。


因此,适当的测试不会测试晦涩的宏 SET_INPUT_PIN,而是质疑它的存在。测试的名称是代码审查。这也会指出以下问题:

  • 绝对没有必要将其设为可变参数宏,因为它应该正好接受 2 个参数——不多也不少。这不过是纯粹的混淆。
  • 此宏不提供类型安全。调用者可以从字面上传递任何东西给它,并且有很大的机会静默传递编译。
  • 以下划线开头,然后是大写字母的标识符是为图书馆保留的。所以不要命名宏 _S 等,它可能会与编译器库冲突。
  • 宏写得很幼稚。那里的平均编码标准将强制执行类似 ( (DDR ## port) &= ~(1 <<(pin)) ) 的东西,在宏参数和最终表达式周围都带有括号。
  • 宏正在使用 1 有符号整数常量,一旦我们在可能具有 16 位 int 的系统上扩展到 16 位寄存器,这很快就会成为一个主要危险。然后我们会到处都是未定义的行为错误。应该是 1u.
  • 存在隐式提升,因此宏实际上 returns 一个 16 位负值 int。这既无益又危险。

按照我在上面评论中所说的,您可以尝试在您的宏中使用 assert()

/*
    Set a pin as an input
    port  (B,C, D or E) 
    pin  - pin to set (0-7)
*/
#define _SET_INPUT_PIN(port,pin) do {                            \
                                       assert(pin>=0 && pin<8);  \
                                       DDR ## port &= ~(1<<pin); \
                                    }  while(0)
#define SET_INPUT_PIN(port, pin) _SET_INPUT_PIN(port, pin)

assert 检查条件并产生错误。如果在C11中编译,可以使用_Static_assert(condition, message)在宏展开后检查条件,作为宏控制。

#define _SET_INPUT_PIN(port,pin) do {                            \
                                       _Static_assert(pin>=0 && pin<8, "Bad pin");  \
                                       DDR ## port &= ~(1<<pin); \
                                    }  while(0)

另请注意,使用参数而不是可变参数会导致更多错误。