29、测试安全关键代码:MISRA-C规范与单元测试的结合、DO-178C标准简介
做嵌入式开发这么多年,我越来越觉得:写安全关键代码,跟写普通代码完全是两码事。
普通代码出bug,顶多重启一下。安全关键代码出bug,那可是要出人命的。航空、汽车、医疗设备,这些领域的代码,容不得半点马虎。
那怎么保证代码安全?两个东西绕不开:MISRA-C 和 DO-178C。前者是编码规范,后者是认证标准。今天我们就聊聊,怎么把单元测试跟它们结合起来。
MISRA-C:不只是“规范”,更是“红线”
MISRA-C,全称是“汽车工业软件可靠性协会C语言规范”。说白了,就是一群老专家把C语言里容易出坑的地方全列了出来,告诉你哪些写法不能用,哪些写法必须注意。
我刚开始接触MISRA-C时,觉得这玩意儿太死板了。比如它禁止使用动态内存分配,禁止使用标准库的某些函数。当时我心想:这还怎么写代码?
后来在航空项目中吃过亏,我才明白——这些限制,每一条背后都是血淋淋的教训。
MISRA-C的核心思想: 通过限制C语言的“灵活性”,消除未定义行为、不确定行为,让代码行为可预测。
MISRA-C与单元测试的结合点
单元测试不只是测功能,更要测“合规”。我建议你在做单元测试时,把MISRA-C检查也纳入进来。具体怎么做?
1. 静态检查先行
别急着写测试用例。先用静态分析工具(比如PC-lint、QAC)跑一遍代码。这些工具能自动检测出MISRA-C违规项。
我曾经在一个项目中,代码写完了才发现有200多处MISRA违规。改起来那叫一个痛苦。所以我的习惯是:每写完一个函数,立刻跑静态检查。违规越少,后面单元测试越省心。
2. 针对“强制规则”写测试
MISRA-C有“强制规则”和“建议规则”。强制规则必须遵守,建议规则尽量遵守。单元测试要重点覆盖强制规则。
举个例子,MISRA-C有一条强制规则:不允许使用隐式类型转换。那你的测试用例就要专门设计场景,验证代码中所有类型转换都是显式的。
// 违规写法:隐式转换
int32_t a = 100;
uint16_t b = a; // MISRA违规!
// 合规写法:显式转换
int32_t a = 100;
uint16_t b = (uint16_t)a; // OK
单元测试里,你可以写一个测试用例,专门检查所有赋值操作是否都用了显式转换。虽然静态工具也能做,但单元测试可以结合运行时数据,验证转换后的值是否在合理范围内。
3. 测试“不可达代码”
MISRA-C要求代码中不能有不可达代码。单元测试正好可以帮你验证这一点。
怎么做?用覆盖率工具。如果你的测试用例跑完,发现某行代码从来没被执行过,那就要小心了——要么是测试用例没写全,要么就是代码里有不可达分支。
我的经验: 在单元测试框架中集成MISRA-C检查,可以用“规则ID”作为测试用例的标签。比如测试用例命名为 TEST_MISRA_RULE_10_1,这样一目了然。
DO-178C:航空软件的“生死线”
DO-178C是航空领域的安全关键软件标准。它把软件安全等级分为A到E级,A级最高(软件失效会导致灾难性事故),E级最低(不影响安全)。
你想想看,飞机上的飞控软件,那必须是A级。这种级别的软件,单元测试的要求极其严格。
| 安全等级 | 失效后果 | 单元测试要求 |
|---|---|---|
| A级 | 灾难性 | 100%语句覆盖 + 100%分支覆盖 + MC/DC覆盖 |
| B级 | 严重 | 100%语句覆盖 + 100%分支覆盖 |
| C级 | 中等 | 100%语句覆盖 |
| D级 | 轻微 | 无强制要求 |
| E级 | 无影响 | 无要求 |
看到这个表,你可能觉得A级要求太变态了。没错,确实变态。但这就是安全关键软件的代价。
DO-178C下的单元测试实践
在DO-178C框架下做单元测试,有几个关键点你必须注意:
1. 测试用例必须可追溯
每个测试用例都要对应到具体的需求。不能凭空写测试。我在项目中,会用需求管理工具(比如DOORS)把需求ID和测试用例ID绑定起来。
2. 覆盖率必须达标
A级软件要求MC/DC覆盖。什么是MC/DC?就是每个条件都要独立地影响决策结果。
举个例子:
if (a > 0 && b > 0) {
// do something
}
要满足MC/DC,你需要至少3个测试用例:
- a>0为真,b>0为真 → 结果为真
- a>0为真,b>0为假 → 结果为假(证明b独立影响结果)
- a>0为假,b>0为真 → 结果为假(证明a独立影响结果)
嗯,这里要注意:MC/DC不是简单的分支覆盖。它要求你证明每个条件都是“有贡献”的。
3. 测试环境必须经过验证
DO-178C要求,测试工具本身也要经过验证。你不能随便拿个开源框架就用。工具必须经过“资格认证”。
我曾经在一个项目中,因为测试框架的一个bug,导致所有测试结果都不可信。最后不得不重新跑一遍所有测试,浪费了两周时间。从那以后,我对测试工具的选型格外谨慎。
警告: 在安全关键项目中,不要使用未经认证的单元测试框架。推荐使用VectorCAST、LDRA Testbed等商业工具,它们有DO-178C认证包。
MISRA-C + DO-178C + 单元测试:三位一体
这三者不是孤立的。我画了一张图,帮你理清它们的关系:
从这张图你能看到:MISRA-C是“怎么做”,DO-178C是“做到什么程度”,单元测试是“怎么证明做到了”。
实战建议:从零开始构建安全关键测试体系
如果你现在要做一个安全关键项目,我建议你按这个步骤来:
- 选工具:确定静态分析工具和单元测试框架。商业工具虽然贵,但省心。
- 定规则:根据项目安全等级,确定要遵守的MISRA-C规则子集。A级项目建议全量遵守。
- 写测试计划:明确测试范围、覆盖率目标、测试环境要求。这个文档要经过评审。
- 编码+静态检查:边写代码边跑静态检查。不要等到代码写完再改。
- 单元测试:每个函数都要有对应的测试用例。测试用例要可追溯、可重复。
- 覆盖率分析:跑覆盖率工具,确保达到目标。A级项目要做MC/DC分析。
- 评审与归档:所有测试结果、覆盖率报告、问题记录都要归档。DO-178C审计时会查这些。
一个小技巧: 在单元测试中,可以写一个“合规性测试套件”,专门用来验证代码是否满足MISRA-C规则。比如测试所有指针操作是否做了空指针检查。这样每次回归测试时,合规性也能得到验证。
说实话,安全关键代码的测试,确实比普通项目繁琐得多。但换个角度想,你写的每一行代码,都可能关系到一条生命。这种责任感,也是我们嵌入式工程师的骄傲。
好了,关于MISRA-C和DO-178C的结合,今天就聊到这里。记住:规范是底线,标准是目标,测试是手段。三者缺一不可。
公众号:蓝海资料掘金营,微信deep3321