29、测试安全关键代码:MISRA-C规范与单元测试的结合、DO-178C标准简介

做嵌入式开发这么多年,我越来越觉得:写安全关键代码,跟写普通代码完全是两码事。

普通代码出bug,顶多重启一下。安全关键代码出bug,那可是要出人命的。航空、汽车、医疗设备,这些领域的代码,容不得半点马虎。

那怎么保证代码安全?两个东西绕不开:MISRA-CDO-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提供编码规范,DO-178C提供认证流程,单元测试提供验证手段 三者结合,才能构建真正安全关键的嵌入式系统

从这张图你能看到:MISRA-C是“怎么做”,DO-178C是“做到什么程度”,单元测试是“怎么证明做到了”。

实战建议:从零开始构建安全关键测试体系

如果你现在要做一个安全关键项目,我建议你按这个步骤来:

  1. 选工具:确定静态分析工具和单元测试框架。商业工具虽然贵,但省心。
  2. 定规则:根据项目安全等级,确定要遵守的MISRA-C规则子集。A级项目建议全量遵守。
  3. 写测试计划:明确测试范围、覆盖率目标、测试环境要求。这个文档要经过评审。
  4. 编码+静态检查:边写代码边跑静态检查。不要等到代码写完再改。
  5. 单元测试:每个函数都要有对应的测试用例。测试用例要可追溯、可重复。
  6. 覆盖率分析:跑覆盖率工具,确保达到目标。A级项目要做MC/DC分析。
  7. 评审与归档:所有测试结果、覆盖率报告、问题记录都要归档。DO-178C审计时会查这些。

一个小技巧: 在单元测试中,可以写一个“合规性测试套件”,专门用来验证代码是否满足MISRA-C规则。比如测试所有指针操作是否做了空指针检查。这样每次回归测试时,合规性也能得到验证。

说实话,安全关键代码的测试,确实比普通项目繁琐得多。但换个角度想,你写的每一行代码,都可能关系到一条生命。这种责任感,也是我们嵌入式工程师的骄傲。

好了,关于MISRA-C和DO-178C的结合,今天就聊到这里。记住:规范是底线,标准是目标,测试是手段。三者缺一不可。


公众号:蓝海资料掘金营,微信deep3321