第4章:自动化测试框架选型——嵌入式测试框架对比与选择策略
说实话,选测试框架这事儿,我当年也纠结过好一阵子。
那时候刚接手一个嵌入式项目,代码量不大,但硬件资源紧张得要命。ROM只剩几十KB,RAM更是抠抠搜搜。我一开始想用Google Test,结果编译完发现,光框架本身就把ROM吃掉了一半。嗯,那会儿我就明白了——嵌入式测试框架选型,跟做菜选锅一样,得看灶台有多大。
4.1 主流嵌入式测试框架对比
我这些年用过的框架不少,挑四个最有代表性的说说:CUnit、Unity、CMock、Google Test。它们各有各的脾气。
| 框架 | 语言 | 资源占用 | 适用场景 | 我的评价 |
|---|---|---|---|---|
| CUnit | C | 中等 | PC端测试、资源较丰富的嵌入式 | 老牌,但有点笨重 |
| Unity | C | 极低 | MCU、资源受限的嵌入式 | 轻量,我的首选 |
| CMock | C | 低(运行时) | 需要mock硬件依赖的场景 | 配合Unity用,绝配 |
| Google Test | C++ | 高 | Linux、ARM Linux、资源充足的系统 | 功能强,但别用在MCU上 |
4.2 各框架深度解析
CUnit:老当益壮,但有点胖
CUnit是最早一批C语言测试框架。它的设计思路跟JUnit很像,有测试套件、测试用例、断言这些概念。我在一个PC端的嵌入式模拟器项目里用过它,功能确实全。
但问题来了——它依赖动态内存分配。你想想看,在裸机MCU上,malloc/free可不是随便能用的。我有个同事硬要在STM32上跑CUnit,结果堆区爆了,调试了一整天。
Unity:我的心头好
Unity是我个人最推荐的嵌入式测试框架。它有多轻?整个框架就两个文件:unity.c和unity.h。我算过,编译后ROM占用不到4KB。
它的用法也简单到令人发指:
#include "unity.h"
void setUp(void) {}
void tearDown(void) {}
void test_led_on(void) {
TEST_ASSERT_TRUE(led_is_on());
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_led_on);
return UNITY_END();
}
你看,没有花里胡哨的东西。我在一个Cortex-M0的项目里,ROM总共32KB,Unity跑得稳稳当当。说白了,它就是为嵌入式而生的。
CMock:搞定硬件依赖
嵌入式测试最头疼的是什么?硬件依赖。你总不能每次测试都真的去点灯、读传感器吧?CMock就是干这个的。
它可以根据头文件自动生成mock函数。比如你有个i2c_read()函数,CMock能生成一个假的版本,让你控制它返回什么值。
Google Test:功能强大,但别乱用
Google Test(简称GTest)功能确实强。参数化测试、死亡测试、mock框架(Google Mock)……应有尽有。但代价也大。
我测过一个项目,GTest编译后ROM占用超过200KB。这在ARM Linux上没问题,但在STM32上?想都别想。我曾经见过有人硬要在FreeRTOS上跑GTest,结果任务栈直接溢出。
4.3 框架选择策略
选框架这事儿,我总结了一个三步法:
- 看资源:先看目标平台的ROM和RAM。小于64KB ROM的,Unity是唯一选择。大于512KB的,可以考虑GTest。
- 看依赖:如果被测代码跟硬件耦合很深,必须用CMock或类似mock工具。Unity+CMock是黄金组合。
- 看团队:团队熟悉C++吗?如果全员C语言老手,别硬上GTest。我见过一个团队为了用GTest,把整个项目改成C++编译,结果兼容性问题一堆。
另外,我建议你做一个快速原型验证。花一天时间,用Unity写几个测试用例,编译到目标板上跑跑。如果跑得通,就继续用。跑不通,再换。别在选型阶段纠结太久。
4.4 我在项目中踩过的坑
坑踩多了,自然就记住了。分享几个典型的:
- 坑一:Unity的TEST_ASSERT_EQUAL是整型比较。我曾经用它比较两个浮点数,结果一直失败。后来才发现,浮点数比较要用TEST_ASSERT_FLOAT_WITHIN。嗯,文档还是要看的。
- 坑二:CMock生成的mock函数默认不检查参数。我有个同事写了个测试,mock了i2c_write(),但没检查传入的数据对不对。结果测试全绿,实际代码里传的参数全是错的。后来我强制团队在mock里加参数检查。
- 坑三:GTest的SetUp/TearDown在嵌入式上可能不执行。我记得有一次在ARM Linux上跑GTest,SetUp函数莫名其妙没调用。查了半天,发现是编译器优化把测试固件给优化掉了。解决方案是加volatile关键字。
- 坑四:别在中断里跑测试。这个我犯过。有一次在中断服务函数里调用了Unity的断言,结果系统直接死机。因为Unity的断言会调用printf,而printf不是中断安全的。
4.5 我的最终推荐
如果你问我,现在新开一个嵌入式项目,用什么测试框架?
我的答案是:Unity + CMock。轻量、稳定、社区活跃。除非你的平台是Linux或者资源极其充裕,否则别碰GTest。
当然,这只是我的个人习惯。你完全可以根据自己的项目情况调整。但记住一点:测试框架是工具,不是目的。别为了用某个框架而强行改造你的项目。
好了,这一章就聊到这儿。下一章我们讲讲如何搭建自动化测试环境,包括如何在Jenkins里跑Unity测试。到时候我会分享一些CI/CD的配置技巧,保证实用。