ISO 26262要求的不只是“代码没语法错误”,而是用实际的测试用例证明代码在运行时确实是安全的。这就是软件单元验证(Software Unit Verification)的核心使命。
软件单元验证:不只是“跑一下看看”
验证的五大目标
根据ISO 26262-6:2018第9章的要求,软件单元验证要达成以下目标:
✅验证实现与设计一致——代码跟设计文档对得上
✅尽早隔离缺陷——在单元层面就把bug扼杀在摇篮里
✅满足ASIL覆盖率要求——不同等级有不同“及格线”
✅形成可追溯证据——每个测试都能追溯到需求
✅保证运行安全稳定——避免死机、失控等风险
💡核心逻辑:单元验证不是“随便测测”,而是用结构化的方法、量化的指标,证明每个函数在运行时都是安全的。
验证的两种大法:静态 vs 动态
ISO 26262的软件验证分为静态验证和动态验证两大类别:
验证类型 | 大白话 | 什么时候做 | 典型方法 |
|---|---|---|---|
🔬静态验证 | “不运行代码,光看代码找问题” | 编码阶段 | 代码走查、技术评审、静态分析 |
🏃动态验证 | “运行代码,看实际行为对不对” | 编码后 | 单元测试、集成测试、故障注入 |
上期我们讲的MISRA检查和静态分析属于静态验证。本期重点讲的是动态验证——真正把代码跑起来,看它到底行不行。
五种单元测试方法:ISO 26262的“官方菜单”
ISO 26262-6:2018的表1列出了五种推荐的软件单元测试方法。不同ASIL等级要求使用的方法不同——等级越高,需要组合的方法越多。
测试方法 | ASIL A | ASIL B | ASIL C | ASIL D | 大白话 |
|---|---|---|---|---|---|
📋基于需求的测试 | ++ | ++ | ++ | ++ | “需求说啥就测啥” |
🔌接口测试 | ++ | ++ | ++ | ++ | “函数之间传数据对不对” |
💉故障注入测试 | + | + | + | ++ | “人为制造故障,看系统扛不扛得住” |
📊资源使用测试 | + | + | + | ++ | “内存会不会爆、CPU会不会满载” |
🔄背靠背测试 | + | + | ++ | ++ | “模型输出和代码输出对不对得上” |
解读:++ = 强烈推荐,+ = 推荐。ASIL-D要用上全部五种方法。这不是“选做”,是“必做”。
方法一:基于需求的测试——“需求说啥就测啥”
核心思想:每一条软件安全需求(SSR),至少要有对应的测试用例来证明它被正确实现了。
怎么做:
把每条SSR拆解成可验证的条目
每条条目至少对应一组测试用例
测试用例写清楚:输入是什么、期望输出是什么、判定点在哪里
ACC示例:
SSR | 测试用例 | 输入 | 预期输出 |
|---|---|---|---|
“系统应在200ms内计算出安全跟车距离” | TC-001 | 本车速度60km/h,前车距离50m | 计算时间<200ms,输出跟车距离≥20m |
方法二:接口测试——“函数之间传数据对不对”
核心思想:验证模块之间的接口契约是否正确——函数调用的参数、返回值、数据类型、范围。
方法三:故障注入测试——“人为制造故障,看系统扛不扛得住”
核心思想:人为引入故障(如内存错误、变量篡改、通信中断),观察软件是否能检测并安全处理。
为什么重要?
很多安全机制在正常运行时根本不会被触发——看门狗只有在程序跑飞时才干活,ECC只有在内存出错时才纠错。如果不做故障注入,你怎么知道这些“备胎”真的能用?
ACC故障注入示例:
故障注入 | 预期响应 |
|---|---|
篡改雷达距离数据为-100m | 系统检测到异常值 → 触发报警 → 进入安全状态 |
模拟内存分配失败 | 系统检测到资源不足 → 降级运行 → 不崩溃 |
模拟函数返回超时 | 超时监控触发 → 进入安全状态 |
方法四:资源使用测试——“内存会不会爆、CPU会不会满载”
核心思想:验证软件在资源受限的嵌入式环境中,不会耗尽内存、不会占满CPU、不会导致系统崩溃。
ACC资源测试示例:
测试项 | 验证目标 |
|---|---|
内存使用峰值 | 不超过可用RAM的80% |
CPU负载 | 峰值不超过可用算力的70% |
堆栈使用 | 不超过分配堆栈的80% |
任务执行时间 | 不超过分配的时间片 |
方法五:背靠背测试——“模型说的和代码做的一样吗”
核心思想:在相同的输入下,比较算法模型的输出和代码的输出是否一致。
适用场景:基于模型开发(MBD)的项目——先用Simulink建模,再自动生成C代码。
ACC背靠背测试示例:
测试步骤 | 操作 |
|---|---|
1 | 在Simulink中运行跟车距离算法模型,输入一组雷达数据 |
2 | 在目标硬件上运行生成的C代码,输入同样的雷达数据 |
3 | 对比两者的输出(减速度请求值)是否一致 |
测试用例怎么设计?四种“武器”帮你搞定
有了测试方法,还得有具体的测试用例。ISO 26262推荐了以下几种测试用例设计方法。
武器一:等价类划分——“同类问题,测一个代表就行”
核心思想:把输入数据分成若干“等价类”,从每个类中选一个代表来测试。
ACC示例:calc_safe_distance(int speed, int weather)
输入参数 | 有效等价类 | 无效等价类 |
|---|---|---|
speed (km/h) | 0-130 | <0, >130 |
weather | 0(晴天), 1(雨天), 2(雪天) | <0, >2 |
测试用例:从每个等价类选一个代表——speed选60(有效)、-1(无效)、131(无效);weather选0、1、2、3(无效)。
武器二:边界值分析——“边界最容易出问题”
核心思想:重点测试边界值——0、最小值、最大值、临界阈值。
ACC示例:
边界 | 测试值 |
|---|---|
speed最小值 | 0 |
speed最大值 | 130 |
speed临界值 | 129, 131(越过边界) |
weather有效范围边界 | 0, 2, -1, 3 |
💡为什么边界值这么重要?绝大多数bug都出现在边界——
if (speed < 130)写成if (speed <= 130),差一个等号就是天壤之别。
武器三:MC/DC覆盖——“每一个条件都要单独验证”
MC/DC是最严格的覆盖率标准,ASIL-D强制要求100%。
要满足MC/DC,你需要测试4种组合,证明每个条件都能独立影响结果:
测试 | sensor_ok | distance < SAFE | 结果 | 证明了什么? |
|---|---|---|---|---|
TC-01 | ✅ TRUE | ✅ TRUE | TRUE | 条件为真 |
TC-02 | ❌ FALSE | ✅ TRUE | FALSE | sensor_ok单独影响结果 |
TC-03 | ✅ TRUE | ❌ FALSE | FALSE | distance单独影响结果 |
TC-04 | ❌ FALSE | ❌ FALSE | FALSE | 条件为假 |
关键点:必须证明每一个条件都能独立改变判定结果——这才是MC/DC和普通分支覆盖的根本区别。
武器四:错误推测法——“凭经验猜哪里容易出问题”
核心思想:根据开发经验和领域知识,猜测哪里最容易出错,针对性设计测试用例。
ACC常见“坑”:
常见问题 | 测试用例 |
|---|---|
除零 | 速度差为0时,相对速度计算是否除零? |
空指针 | 传入NULL指针会不会崩溃? |
数组越界 | 缓冲区写入是否超过分配大小? |
状态机异常 | 非法状态转换是否被拦截? |
覆盖率要求:ASIL等级的“及格线”
这是ISO 26262最硬核的要求之一。不同ASIL等级对代码覆盖率的要求不同。
ASIL等级 | 语句覆盖 | 分支覆盖 | MC/DC覆盖 |
|---|---|---|---|
| ASIL A | ≥80% | ≥70% | 不强制 |
| ASIL B | ≥80%~100% | ≥80%~100% | 可选 |
| ASIL C | 100% | 100% | ≥90%~100% |
| ASIL D | 100% | 100% | 100% |
ASIL-D要求MC/DC达到100%——这意味着代码中每一个条件的所有可能组合都必须被测试覆盖。
为什么覆盖率这么重要?
ISO 26262要求度量结构化代码覆盖率,以证明测试的完整性。简单说:
覆盖率不是为了“凑数字”,而是为了证明“你确实测到了每一个可能出问题的地方”。
覆盖不足怎么办?
如果覆盖率不达标,需要:
分析未被覆盖的代码路径
补充测试用例覆盖这些路径
优先补充关键分支和异常路径
- 覆盖不足视为未达标
——用例全通过但覆盖不足,不能算过关
单元测试的“武器库”:主流工具一览
手动做单元测试不现实——代码量动辄几万行,靠人工跑用例得跑到天荒地老。
商业工具
工具 | 特点 | 适用场景 |
|---|---|---|
| Tessy | 专为嵌入式C/C++设计,已通过TÜV认证,支持自动化执行测试并生成报告 | ASIL-D项目首选 |
| Cantata | 支持在主机和目标平台自动化单元/集成测试 | 高安全等级项目 |
| VectorCAST | 完整的嵌入式测试平台 | 大型项目 |
开源/通用框架
框架 | 特点 | 适用场景 |
|---|---|---|
| Google Test | 功能强大,社区活跃 | AUTOSAR AP平台 |
| CppUTest | 轻量级,适合嵌入式C | 资源受限的ECU |
| Catch2 | 单头文件,编译快 | 快速原型开发 |
选择工具的要点:工具本身需要有功能安全认证(如Tessy已通过TÜV认证),才能用它产出的测试报告作为功能安全证据。
实战:ACC控制器单元验证全流程
把以上所有内容整合起来,ACC控制器的单元验证完整流程是这样的。
Step 1:确定验证范围
软件单元 | 功能 | ASIL | 验证方法 |
|---|---|---|---|
get_radar_distance() | 读取雷达数据 | D | 需求测试+接口测试+故障注入+资源测试+背靠背 |
calc_safe_distance() | 计算安全距离 | D | 全部五种方法 |
check_following() | 判断跟车状态 | D | 全部五种方法 |
Step 2:设计测试用例(以calc_safe_distance()为例)
用例ID | 测试方法 | 输入(speed, weather) | 预期输出 | 覆盖目标 |
|---|---|---|---|---|
TC-001 | 基于需求 | (60, 0) | 26 | 正常晴天 |
TC-002 | 基于需求 | (60, 1) | 36 | 正常雨天 |
TC-003 | 基于需求 | (60, 2) | 46 | 正常雪天 |
TC-004 | 接口测试 | (-1, 0) | -1 | 无效speed |
TC-005 | 接口测试 | (131, 0) | -1 | speed超上限 |
TC-006 | 接口测试 | (60, 3) | -1 | 无效weather |
TC-007 | 边界值 | (0, 0) | 20 | speed=0 |
TC-008 | 边界值 | (130, 0) | 33 | speed=130 |
TC-009 | 故障注入 | 模拟speed读取失败 | 返回-1 | 异常处理 |
TC-010 | 资源测试 | 连续调用1000次 | 内存无泄漏 | 资源稳定性 |
Step 3:执行测试并采集覆盖率
使用Tessy等工具执行测试用例,自动采集覆盖率数据。
覆盖率报告示例:
覆盖类型 | 目标 | 实际 | 状态 |
|---|---|---|---|
语句覆盖 | 100% | 100% | ✅ |
分支覆盖 | 100% | 100% | ✅ |
MC/DC覆盖 | 100% | 100% | ✅ |
Step 4:建立可追溯性
每个测试用例都必须能追溯到对应的需求。
审核员会查什么:你说“测过了”——证据呢?测试用例在哪?覆盖率报告在哪?每条SSR都有对应的测试用例吗?可追溯性是审核必查项。
单元验证中容易踩的“坑”
坑1:只测“正常情况”,不测“异常情况”
❌ 只验证“输入合法时输出正确”
✅ 必须测试非法输入、边界值、异常路径——安全系统的代码必须在任何情况下都不崩溃
坑2:用例全过了,但覆盖率为0
❌ “所有测试用例都通过了,肯定没问题”
✅用例全通过但覆盖不足,不能算过关——没测到的代码,就是潜在的雷
坑3:忘记做故障注入
❌ “功能都正常,不用测故障”
✅安全机制在正常运行时根本不会触发——不做故障注入,你怎么知道它们真的能用?
坑4:没有建立可追溯性
❌ 测试用例和需求之间没有关联
✅ 建立SSR ↔ 测试用例 ↔ 测试结果的完整追溯链。