❄️ 我的个人专栏:
《智能软件工程AI4SE》
《嵌入式面试总结》
《嵌入式处理器架构解析》
《嵌入式与虚拟化》
《嵌入式软件测试》
🌟 Simplicity is the ultimate sophistication
摘要:本文是嵌入式软件覆盖率测试系列第十六篇,围绕 VectorCAST 在嵌入式目标机上的覆盖率实战展开。文章从工程搭建与交叉编译链路调通讲起,先以语句覆盖起步,再推进到分支覆盖,最终打通 MC/DC,并给出各覆盖等级的用例设计方法与判定依据。同时结合探针数据回读失败、优化等级导致覆盖率失真、中断与多任务并发路径、覆盖率与需求追溯等常见踩坑点,说明如何在资源受限的嵌入式环境中稳定拿到真实可信的覆盖率数据。
一、为什么要在嵌入式目标机上做覆盖率
覆盖率测试在宿主机上跑单元测试并不难,难的是把覆盖率数据从真实目标板上拿回来。嵌入式软件的特殊性在于:编译器不同、运行时库不同、内存和 Flash 资源紧张、调试接口带宽有限,很多在 PC 上理所当然的插桩手段在目标机上根本跑不起来。
VectorCAST 的价值在于它把「测试用例管理 + 插桩 + 覆盖率统计 + 报告生成」串成了一条链路,并且对主流嵌入式交叉编译工具链(GCC、IAR、Keil、Tasking、Green Hills 等)都有成熟的适配模板。本文以一个典型的 Cortex-M 裸机工程为例,演示如何从零把覆盖率打到 MC/DC。
二、环境准备与工程搭建
2.1 工具与版本
- VectorCAST:VectorCAST/C++ 2023 及以上版本,需包含 Embedded 与 Coverage 授权。
- 交叉编译器:arm-none-eabi-gcc(示例),其他工具链配置思路一致。
- 调试探针:J-Link / ST-Link,用于下载与在线调试。
- 目标板:Cortex-M4,Flash 512KB,RAM 128KB(示例配置)。
2.2 创建 VectorCAST 工程
在 VectorCAST Manager 中新建工程,选择 Embedded 类型,指定交叉编译器路径与目标机配置。关键是把编译、链接、下载、运行四个环节的命令行模板配置正确,后续所有覆盖率测试都依赖这套模板。
# 交叉编译器配置示例
CC = arm-none-eabi-gcc
CXX = arm-none-eabi-g++
SIZE = arm-none-eabi-size
OBJCOPY = arm-none-eabi-objcopy
2.3 导入被测单元
把待测源文件(如 motor_ctrl.c、can_drv.c)加入工程,VectorCAST 会自动解析函数列表并生成测试环境。此时先不要急着开覆盖率,先确认「能编译、能下载、能跑通一个空用例」,这是后续所有工作的地基。
三、从语句覆盖起步
3.1 打开语句覆盖插桩
在环境配置中启用 Coverage,选择 Statement 级别。VectorCAST 会在编译阶段对目标文件插入计数探针,探针数据在运行结束后通过调试接口回读。
/* 被测函数示例:电机转速限幅 */
int motor_limit_speed(int target, int max_speed)
{
int result = target;
if (target > max_speed) {
result = max_speed;
}
return result;
}
3.2 编写最小用例集
语句覆盖的目标是让每条可执行语句至少执行一次。对上面的函数,只需要两个用例:target <= max_speed 与 target > max_speed,即可覆盖 result = max_speed; 这条赋值语句。
| TC01 | 100 | 200 | 100 |
| TC02 | 300 | 200 | 200 |
3.3 查看语句覆盖结果
执行完毕后,VectorCAST 会在 Coverage 视图中以颜色标注已执行与未执行的语句。此时通常能轻松达到 90% 以上,但要注意:语句覆盖高并不代表逻辑正确,短路求值、异常分支、边界条件都可能被掩盖。
四、推进到分支覆盖
分支覆盖(Decision Coverage)要求每个判定表达式的真、假两个方向都至少走一次。对于 if (target > max_speed),TC01 走假分支、TC02 走真分支,天然满足。
真正容易漏的是复合条件与循环边界。例如:
int can_check_frame(const frame_t *f)
{
if (f != NULL && f->dlc > 0 && f->dlc <= 8) {
return 1;
}
return 0;
}
分支覆盖只关心整个 if 的真假,因此两个用例就能满足。但每个子条件的独立影响完全没有被验证,这正是 MC/DC 要解决的问题。
五、打通 MC/DC
5.1 MC/DC 的判定要求
修正条件/判定覆盖(MC/DC)要求:
对 A && B && C 三个条件,最少需要 4 个用例(N+1 原则)。
5.2 设计 MC/DC 用例
| TC11 | NULL | – | F | – | – | F |
| TC12 | 有效 | 0 | T | F | – | F |
| TC13 | 有效 | 9 | T | T | F | F |
| TC14 | 有效 | 8 | T | T | T | T |
逐条验证独立性:TC11 与 TC12 对比,仅 A 变化即翻转结果;TC12 与 TC13 对比,仅 B 变化即翻转;TC13 与 TC14 对比,仅 C 变化即翻转。满足 MC/DC。
5.3 在 VectorCAST 中启用 MC/DC
把 Coverage 级别切换为 MC/DC,重新编译插桩。注意 MC/DC 插桩会显著增加代码体积与执行开销,在 Flash 紧张的工程中需要评估:
- 优先对安全关键模块开启 MC/DC,非关键模块保持语句或分支覆盖;
- 使用 -Os 时插桩可能被优化掉,建议覆盖率构建使用 -O0 或 -Og;
- 探针数据缓冲区要预留足够空间,避免运行中溢出导致数据丢失。
六、常见踩坑与应对
6.1 覆盖率数据回读失败
最常见的原因是探针缓冲区地址与链接脚本冲突,或调试接口在运行结束后未正确复位。建议在链接脚本中显式划分一段 .covdata 段,并在 VectorCAST 配置中同步该地址。
6.2 优化等级导致的覆盖率失真
编译器内联、常量折叠、死代码消除都会让插桩点与源码行对不上。覆盖率构建务必关闭激进优化,并在报告中核对行号映射。
6.3 中断与多任务场景
裸机中断服务程序、RTOS 任务切换都会带来并发执行路径。VectorCAST 支持在目标机上以桩函数方式隔离中断,但真实并发路径的覆盖率仍需结合硬件在环测试补充。
6.4 覆盖率与需求的对应
覆盖率只是手段,不是目的。建议把每个用例与需求条目建立追溯关系,在报告中同时呈现「覆盖率数值」与「需求覆盖情况」,避免为了刷数字而写无效用例。
七、小结
从语句覆盖到 MC/DC,VectorCAST 提供了一条相对完整的嵌入式覆盖率落地路径。关键不在于工具本身,而在于三点:一是把交叉编译与下载运行链路调通;二是按覆盖等级分层推进,先语句、再分支、最后 MC/DC;三是正视嵌入式环境的资源约束与优化干扰,保证覆盖率数据真实可信。下一篇将继续讨论覆盖率报告的组织与评审要点。



