1 软件单元验证用例导出方法
为确保软件单元测试的测试案例规范符合9.4.2要求,应通过表8所列方法开发测试用例。
表8 软件单元测试用例的得出方法:
备注:
a 等价类可以根据输入和输出的划分来确定,这样就可以为每个类选择一个有代表性的测试值。
b 该方法用于接口、接近边界的值、与边界交叉的值及超出范围的值。
c 错误猜测测试可以根据通过“经验教训”过程和专家判断收集的数据进行。
1. 需求分析
专业解释:
这是软件单元测试用例生成的根本来源和起点。它系统地审查分配给软件单元的功能性安全需求和非功能性需求(例如性能、资源约束)。测试用例需要明确覆盖需求中规定的每个功能、输入、输出、前提条件、后置条件以及需求之间的逻辑关系(如顺序、并发、互斥)。目的是确保需求文档中定义的每一个预期行为都有对应的测试进行验证,达成需求的“完全覆盖”。通俗解释:
就像照着说明书来检查一样。工程师仔细阅读软件单元“应该做什么”的描述(这份说明书叫需求),然后根据里面的每一条描述,一条一条地设计测试方法,看看软件在实际运行中是不是真的按照说明书说的那样做了。说明书里提到的东西,一个都不能漏掉测试。汽车行业实施示例:
需求: “当车速信号超过 180 km/h 时,单元 X 应当向控制单元 Y 发送‘超速警告’信号。”
测试用例设计:
- 准备一个模拟车速为 179 km/h 的信号作为输入给单元 X。预期:单元 X 不发送‘超速警告’信号。
- 准备一个模拟车速为 180 km/h 的信号作为输入给单元 X。预期:单元 X 发送‘超速警告’信号给单元 Y。
- 准备一个模拟车速为 181 km/h 的信号作为输入给单元 X。预期:单元 X 发送‘超速警告’信号给单元 Y。
说明: 这里直接根据需求描述的触发条件和预期动作来设计测试,确保软件在触发点前后都正确响应。
2. 等价类划分与等价类分析
专业解释:
这是一种基于输入域分区的测试技术。它将所有可能的输入数据划分为若干“等价类”,在同一个等价类中的数据元素,预期软件的行为是相同的(等价)。核心思想是:测试一个等价类的代表值就足以推断该类中所有值的测试结果,从而显著减少冗余测试用例的数量。等价类又分为有效等价类和无效等价类。有效等价类包含符合需求规定的合法输入;无效等价类包含不符合规范、非法或不正确的输入(如数据类型不符、超出范围、空指针等)。测试用例需要覆盖每个有效和无效等价类。通俗解释:
把软件可能收到的各种输入,分成几个“小组”。同一小组里的数据,软件处理的方式应该是一模一样的。这样就不用把小组里所有数据都测一遍,只要挑出小组里的“代表”,比如一个或几个典型例子来测试就够了。同时,不仅要测正常该输入的“好数据”(有效小组),还要故意输入一些“坏数据”(无效小组),比如错的格式、超大的数、负数、空格等等,看软件能不能识别错误并且正确处理(比如报错、拒绝执行)。汽车行业实施示例:
软件单元功能: 计算发动机建议的换挡转速 (基于当前发动机转速 R 和节气门开度百分比 P)。需求规定 R 有效范围为 [800 rpm, 7000 rpm],P 有效范围为 [0%, 100%]。
等价类划分:
- 有效等价类 (V): R = [800, 7000], P = [0, 100]
- 无效等价类 (I): R 7000, P 100
分析: 有效等价类内部行为理论上应该是连续的,选择内部一些点(如 R=1000, P=10%; R=4000, P=50%; R=6800, P=90%)进行测试就够了。而无效等价类需要单独测试:
- 测试 R=799 rpm (任何 P, 如 50%):预期:单元检测到错误,输出错误代码或默认安全值。
- 测试 R=7001 rpm (任何 P, 如 50%):同上。
- 测试 P=-1% (任何 R, 如 3000 rpm):同上。
- 测试 P=101% (任何 R, 如 3000 rpm):同上。
说明: 避免了测试每个转速和每个开度组合(那将是天文数字),用等价类大大提高了效率。
3. 边界值分析
专业解释:
这是等价类划分技术的一种补充和强化策略,特别关注输入域边界附近的取值。经验表明,软件在边界值点或其邻近点(刚好在边界内、刚好在边界上、刚好在边界外)发生错误的概率最高。因此,该方法针对每一个输入变量的有效范围边界(最大值、最小值、特殊阈值)及其紧邻的外部和内部值设计测试用例。它通常在等价类划分的基础上进行,为每个边界点生成测试点(最小值、最小值+1、最大值-1、最大值等)。通俗解释:
重点检查软件在“边界线”上表现怎么样。比如软件能处理的最大速度、最小电压、临界温度等。测试时会特意输入 刚好在边界线上(如最大值)、比边界大一点点、比边界小一点点的数据,看看软件会不会在极限情况下出错、崩溃或者做出危险动作。例如,输入是 0 到 100,那就特意测试 -1, 0, 1, 99, 100, 101 这些关键点。汽车行业实施示例:
软件单元功能: 根据电池电压 (V) 判断车辆启动状态。需求规定:V 16.5V 判为“电压过高警告”。
边界值分析:
- 低边界: 测试 V = 8.9V (刚好低于9.0):预期:输出“电池过低无法启动”。
- 低边界附近: 测试 V = 8.99V(或根据需要选择一个略小于9.0的值)。
- 低边界值: 测试 V = 9.0V:预期:输出“准备就绪可启动”。
- 低边界内: 测试 V = 9.1V:预期:输出“准备就绪可启动”。
- 高边界值: 测试 V = 16.5V:预期:输出“准备就绪可启动”。
- 高边界附近: 测试 V = 16.49V(或根据需要选择一个略小于16.5的值)。
- 高边界: 测试 V = 16.5V + ε (例如 16.51V):预期:输出“电压过高警告”。
- 高边界外: 测试 V = 17.0V:预期:输出“电压过高警告”。
说明: 严格测试 9.0V 和 16.5V 这两个关键阈值及其紧邻上下区域,确保边界判断逻辑绝对正确,避免临界点附近误判导致车辆启动失败或电气系统过压损坏。
4. 基于知识或经验的错误猜测
专业解释:
这是一种依赖于测试人员(往往是经验丰富的工程师或软件安全专家)的领域知识、以往故障模式数据库积累和问题诊断直觉的启发式方法。测试人员基于对特定技术(如指针运算、浮点精度、线程同步、资源管理)、平台特性、历史 Bug、常见软件缺陷模式、标准或行业最佳实践违背点的理解,推测软件单元在哪些地方可能容易出现错误或存在潜在安全漏洞。然后,针对这些“容易出错点”设计和执行特定的、可能不会被前述结构化方法轻易覆盖到的测试用例。这是对结构化方法(需求分析、等价类、边界值)的重要补充。通俗解释:
有点像一个经验丰富的“老法师”来给软件挑毛病。工程师们凭借自己多年的工作经验和对汽车软件特性的熟悉程度,特别是以前项目中踩过的坑、修过的 Bug,加上对软件常见的“坏习惯”(比如资源泄漏、数值溢出、并发冲突、边界没处理好)的了解,主动去猜想“这个软件在什么意想不到的情况下可能出错?”。然后根据这些猜想设计一些“刁钻”的测试案例,甚至模拟一些极端或不寻常的场景(比如突然断电、传感器信号突然消失、被输入乱七八糟的数据)来进行攻击测试。汽车行业实施示例:
常见错误/风险点:
- 指针和内存: 对指针进行越界访问后不检查有效性。测试用例:传递一个 NULL 指针或已释放内存的地址给单元,看是否会导致崩溃或产生不可预测行为。模拟内存分配失败的情况。
- 数值处理: 变量范围不够大导致溢出。测试用例:输入一个极端大的发动机扭矩值或积分值,看是否会溢出导致控制失效。
- 并发和时序: 多个任务同时访问共享资源(如全局变量)可能引起的竞争条件。测试用例:设计测试场景让单元在极高调用频率下执行,或者同时触发多个中断访问同一数据。
- 硬件/环境故障模拟: 传感器瞬间失效或信号跳变。测试用例:让轮速传感器信号突然从 100km/h 跳变为 0km/h(模拟轮速传感器失效),看电子稳定控制系统软件是否会导致误判、过度修正或宕机。测试 CAN 总线通信中断时单元的降级模式是否符合安全要求。
- 极端操作条件: 车辆在极寒(-40°C)或极热(85°C)环境下启动时,软件初始化是否能正确处理时间延迟和资源加载。
- 特定算法陷阱: 如除法操作可能除以零。测试用例:输入可能导致除数为零的计算参数。
- 非预期输入序列: 如要求必须先调用 FunctionA 再调用 FunctionB,但故意先调用 FunctionB。或者在电池管理单元测试中,连续发送完全冲突的充电和放电指令。
- 安全机制测试: 故意触发软件单元内部设计的安全机制(如看门狗喂狗逻辑不正确、程序流监控报错等),看机制是否有效触发并执行预期安全动作(如进入安全状态)。
整合与应用:
在实际项目中,这四种方法必须结合使用,缺一不可:
- 需求分析是根基: 确保测试覆盖了所有指定的功能。
- 等价类和边界值是结构化的“主力军”: 高效、系统性地覆盖输入域和关键边界点。
- 错误猜测是关键的“补充火力”: 探测那些结构化的常规测试难以覆盖的、意想不到的角落,特别是由于实现缺陷、环境异常或攻击性输入导致的问题,对功能安全尤其重要。
在汽车行业,严格的软件单元测试是确保车辆电子电气系统功能安全(FuSa)的基石。通过系统性地应用这些方法导出测试用例并严格执行,可以显著降低软件单元中的系统性故障风险,满足 ASIL 等级(A、B、C、D)的安全性要求。
2 测试用例完整性度量标准
为了评估测试用例的完整性并证明没有非预期的功能,应确定在软件单元层面的要求覆盖率,同时应按照表9列出的度量对结构覆盖率进行测定。如果认为已实现的结构覆盖率不充分,应定义额外的测试用例或提供接受理由。
表9 软件单元层面的结构覆盖率度量:
注1:没有理论基础的结构覆盖率的目标值或低目标值都被认为是不充分的。
示例1:结构覆盖率分析可以显示基于需求的测试用例的不足、要求的缺陷、无作用码、无效代码或非预期的功能。
示例2:基于可接受的无作用码(例如:用于调试的代码)或不同软件配置的代码区段可以给出接受所达到的覆盖率水平的理由;或可以使用补充方法(例如:检查)验证未被覆盖的代码。
示例3:理论基础可以建立在当前最佳状态基础上。注2:结构覆盖率可以通过使用适当的软件工具来确定。
注3:在基于模型开发的情况下,结构覆盖率分析可以利用相似的模型结构覆盖率度量在模型层面进行。
示例4:如果在模型级别执行的结构覆盖率被证明是等价的,那么对它的分析可以替换源代码覆盖率度量,且其理论基础基于这样一个证据:覆盖率代表代码级别。注4:如果使用测量代码来确定结构覆盖率的程度,那么就有必要提供证据证明该测量对测试结果没有影响。这可以通过使用非测量代码重复进行具有代表性的测试案例来实现。
1. 语句覆盖率 (Statement Coverage, SC)
- 定义: 度量单元验证测试用例集合执行了软件单元中所有可执行语句的程度。
可执行语句
通常指赋值语句、函数调用、控制流语句(如if
,for
,while
,switch
等)本身(但不包括其内部的条件)。- 非执行语句(如注释、声明、空行等)不计入。
- 作用:
- 最基础的覆盖指标,用于发现“死代码” (Dead Code) - 那些在正常或错误条件下都永远不会执行的语句。
- 识别部分未测试的代码路径。
- 满足 ASIL A/B 级别的基本要求或作为更高级别覆盖的基础。
- 公式:
SC = (已执行的可执行语句数 / 总可执行语句数) * 100%
- 实施过程:
- 编译设置: 启用编译器的代码插桩功能(通常生成带插桩点的可执行文件)。
- 执行测试: 运行所有单元测试用例。
- 覆盖分析: 收集插桩点执行数据(哪些语句被执行),由覆盖率工具(如 VectorCAST, LDRA Testbed, Tessy, CTC++, Gcov, BullseyeCoverage)分析并计算 SC。
- 报告与评估: 查看覆盖率报告,找出未覆盖的语句,分析原因(逻辑错误?测试用例缺失?异常路径未覆盖?)。
- 提升覆盖率: 针对未覆盖语句设计新的或修改现有测试用例。目标通常是 100% SC (对于所有 ASIL 级别)。
- 局限性:
- 无法保证控制流决策的逻辑被充分测试(即使语句都执行了,决策的真/假分支可能没覆盖全)。
- 无法保证所有条件组合或独立影响被测试到。
- 实例:
考虑以下函数片段:
int calc(int a, int b, int op) { int result = 0; // Executable stmt 1 if (op == 1) { // Executable stmt 2 (The 'if' itself) result = a + b; // Executable stmt 3 } else { result = a - b; // Executable stmt 4 } return result; // Executable stmt 5 }
- 总共有 5 条可执行语句(注释行不计)。
- 测试用例
calc(2, 3, 1)
:- 执行
result = 0;
->if (op == 1)
->result = a + b;
->return result;
- 覆盖了 stmt 1, 2, 3, 5。
- 执行
- 未覆盖 stmt 4 (
result = a - b;
)。 - SC = (4 / 5) * 100% = 80%。
- 为了达到 100% SC,需要增加一个执行
else
分支的测试用例,如calc(5, 2, 2)
(假设op !=1
走else
)。
2. 分支/判定覆盖率 (Branch Coverage, Decision Coverage, DC)
- 定义: 度量单元测试用例集合执行了软件单元中所有控制流分支的程度。每个控制流点(
if
,switch
,while
,for
,?:
,&&
,||
)的真值分支 (true
) 和假值分支 (false
) 都应被执行。 - 也被称为边覆盖 (Edge Coverage),因为它关注控制流图 (CFG) 中边的覆盖。
- 不同于条件覆盖率 (Condition Coverage),后者关注布尔表达式中每个原子条件的真假值,但不一定组合成所有可能的判定结果。
- 作用:
- 确保决策点所有的可能出口路径都被测试到。 防止遗漏关键的
true
或false
逻辑路径。 - 满足 ASIL B 级别的要求,也是 ASIL C/D 级别 MC/DC 的先决条件。
- 公式:
DC = (已执行的分支数 / 总分支数) * 100%
- 注意:对于简单的
if-else
,通常算作一个判定点,有 2 个分支(true
和false
)。对于if
没有else
,没有显式的else
分支也被视为一条隐式的空分支(从判断点直接流出),也需要覆盖。 - 实施过程:
- 与 SC 实施过程类似(步骤 1-3),覆盖率工具同样会记录每个控制流点的真假分支是否被执行。
- 分析报告中的
DC
(或类似指标,有时工具可能称其为 “Condition Coverge” 或 “Branch Condition Coverage” - 注意区分术语)。 - 识别并覆盖未执行的分支。目标通常是 100% DC。
- 局限性:
- 对于一个由多个条件组成的布尔表达式(例如
if (A && B)
),100% DC 可以通过只测试两种路径实现:A=true, B=true
(判定结果为true
,覆盖true
分支)A=false
(B
的值无关,因为短路计算,判定结果为false
,覆盖false
分支)
- 这种情况下,
A=true, B=false
导致判定结果为false
的路径没有被测试!DC 没有揭示条件组合的问题,无法保证每个条件的独立影响被测试。这就需要 MC/DC。 - 实例:
使用上面的calc
函数:
int calc(int a, int b, int op) { int result = 0; if (op == 1) { // 这是一个判定点 result = a + b; } else { // 显式的else分支 result = a - b; } return result; }
- 这个
if-else
结构构成了一个判定点,有 2 个分支:true
分支(op == 1
为真,执行加法)。false
分支(op == 1
为假,执行减法)。
- 测试用例
calc(2, 3, 1)
:op == 1
为真 -> 覆盖true
分支。
- 测试用例
calc(5, 2, 2)
:op == 1
为假 -> 覆盖false
分支。
- 两个分支均被覆盖 -> DC = 100%。
- 如果只有
calc(2, 3, 1)
,只覆盖了true
分支,DC = 50%。
- 定义: 度量单元测试用例集合执行了软件单元中所有控制流分支的程度。每个控制流点(
3. 修正条件/判定覆盖 (Modified Condition/Decision Coverage, MC/DC)
- 定义: 这是 ISO 26262 对 ASIL C/D 级别强制执行的要求。它比 DC 更严格。
- 目标: 保证在一个布尔判定中(由多个条件组成),每个条件的独立影响于该判定的最终结果都能被演示。
- 核心要求:
- 判定覆盖 (Decision Coverage): 每个判定的所有可能结果必须至少被测试一次(
true
和false
)。 - 条件覆盖 (Condition Coverage): 判定中每个条件的所有可能值(
true
和false
)必须至少被测试一次。 - 独立影响 (Independence): 对于每个条件
C
, 必须存在两对测试用例:- 两对用例中,除了条件
C
的值不同外,其他所有条件的值完全相同。 - 条件
C
值的改变,独立地导致了整个判定结果的改变(即一对用例判定结果不同)。
- 两对用例中,除了条件
- 判定覆盖 (Decision Coverage): 每个判定的所有可能结果必须至少被测试一次(
- 满足独立影响要求即可自动满足条件覆盖和判定覆盖。
- 优化:只需
n+1
个测试用例(n 是判定中条件的个数)就能满足 MC/DC(优于条件组合覆盖2^n
)。 - 作用:
- 最有效地揭示由多个条件组合的逻辑错误、缺失需求和规格误解。
- 确保逻辑表达式中的每个条件都能独立控制输出(在特定输入组合下)。
- 对安全关键软件提供高水平的可靠性保证。
- 实施过程:
- 识别目标判定: 定位那些由多个条件组成的复杂布尔表达式(通常出现在
if
,while
,switch
条件或布尔变量赋值中)。 - 分析条件: 确定判定表达式中的所有原子布尔条件。
- 设计满足 MC/DC 的测试用例: 对每个条件
C_i
,找出或设计满足其独立影响要求的用例对。关注哪些条件可以固定以观察C_i
的变化是否独立影响结果。 - 执行测试用例: 运行这些精心设计的测试用例。
- 覆盖分析: 使用支持 MC/DC 分析的覆盖率工具(如 VectorCAST, LDRA Testbed, Tessy, CTC++, Cantata)收集和分析数据。工具会报告每个条件的独立影响是否已被证实(
MC/DC True
)以及整体 MC/DC 百分比。 - 报告与评估: 识别未被证明独立影响的条件。目标必须是 100% MC/DC。
- 提升覆盖率: 为每个未达标的条件设计或补充缺失的用例对。
- 局限性: 主要在于实施的复杂性、分析和设计测试用例所需的时间/精力,尤其对于庞大或嵌套复杂的判定。
总结与关键实施建议:
- 工具依赖性强: 可靠的结构覆盖分析高度依赖专业的覆盖率工具。需要支持 SC、DC 和 MC/DC,并能与目标编译器和嵌入式环境(或模拟器/仿真器)协同工作。
- 环境模拟至关重要: 嵌入式环境的限制(资源、硬件依赖)常需使用软件在环(SIL)、处理器在环(PIL)测试或功能强大的模拟环境。
- 100% 目标: ISO 26262 期望(对相关安全部分)达到尽可能高的覆盖率(接近100%),尤其是 SC/DC/MC/DC。所有未覆盖点都必须进行合理性论证(Justification)(如证明该代码不可能执行,或与安全无关)。
- 测试用例设计优先: 覆盖率是测试充分性的证据,设计出能够暴露问题的好的测试用例才是核心。覆盖率是衡量测试完整性的度量工具。
- 关注异常和边界: 单元测试必须覆盖错误输入、边界值条件以及中断/异常处理路径,这些地方往往是缺陷高发区。
- 集成到开发流程: 将覆盖率分析集成到持续集成(CI)流水线中,使其成为自动化构建和测试的一部分。
- 评审: 覆盖率结果和未覆盖点的合理性论证需要经过评审。
3 验证环境要求
对于软件单元验证的测试环境,应该尽可能地接近目标环境。如果软件单元测试不是在目标环境下执行,应分析源代码和目标代码的差异及测试环境和目标环境之间的差异,以便在后续测试阶段的目标环境中,定义额外的测试。
注1:测试环境和目标环境之间的差异,可出现在源代码或目标代码中,例如,由于不同处理器的数据字和地址字的不同位宽引起的差异。
注2:根据测试范围,使用适当的测试环境(例如目标处理器、处理器仿真器或开发系统)执行软件单元测试。
注3:软件单元测试可以在不同的环境中执行,例如:
- 模型在环测试;
- 软件在环测试;
- 处理器在环测试;
- 硬件在环测试。
(后续介绍以上四类测试)
注4:对基于模型的开发,在模型层面执行软件单元测试,随后,在模型和目标代码之间进行背靠背的比较测试。背靠背的比较测试用于确保关于测试对象的模型表现等同于自动生成的代码。
4 软件单元验证的工作产品
软件单元验证报告(Software Unit Verification Report): 汇总所有验证活动(静态分析、动态测试、评审等)的结果、覆盖度指标、识别的缺陷及其状态、以及是否符合入口/出口准则的结论。详细记录每个测试用例的执行结果、日志、通过的证明或缺陷报告。
(测试)覆盖度报告((Test) Coverage Report): 展示测试活动对代码(如语句覆盖、分支覆盖、MC/DC覆盖)或需求(需求覆盖)的覆盖程度,证明验证的充分性(尤其针对ASIL D的高要求)。
软件单元测试规范(Software Unit Test Specification):内容:由需求输入衍生而来。详细描述针对每个软件单元的具体测试用例、测试输入数据、预期输出结果、测试环境设置、以及执行的先决条件。作用:核心执行依据。 直接指导动态测试(如单元测试)的执行。
📌 应用提示:
- ASIL等级决定严格度:ASIL等级越高(如D级),对测试覆盖度(如MC/DC)和文档完备性要求越严格
- 工具链需认证:若使用自动化测试/分析工具,需符合ISO26262-8的工具鉴定(Tool Qualification)要求
- 版本一致性:所有输入文档必须保持版本同步,避免因文档迭代导致验证偏差