是否可以对嵌入式软件的 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)
另请注意,使用参数而不是可变参数会导致更多错误。
我是如何为嵌入式 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)
另请注意,使用参数而不是可变参数会导致更多错误。