摘要:
根据图一软件开发阶段V模型,软件单元设计和实现之后,需要对相应的软件设计进行测试验证即V模式右边内容。具体包括:软件单元验证、软件集成和验证、嵌入式软件验证。本文着重阐述功能安全软件开发阶段中这三个阶段的测试验证活动。
图一:软件开发阶段V模型
1、软件测试验证方法
1.1 软件测试验证对象
软件单元验证、软件集成和验证、嵌入式软件验证分别对应的不同的测试对象,具体如下图所示:
图二:软件测试验证对象
1.2 软件测试验证方法
ISO 26262-6:2018 从第9章节到第11章节分别对软件单元验证、软件集成和验证、嵌入式软件验证这三部分进行了详述,其中对根据ASIL等级推荐了不同的验证方法,如下图所示:
图三、软件单元验证方法
图四、软件集成验证方法
图五、嵌入式软件的测试方法
从图三、图四、图五可以看到软件单元设计的验证以走查、检查、审查、分析、测试等为主要验证方法;软件集成验证以静态分析、资源评估(主要包括消耗的CPU资源、RAM、ROM等)、测试等为主要验证方法;嵌入式软件的测试方法主要用测试来进行验证。虽然属于软件开发中的不同子阶段,但是很多测试方法是共同的,比如故障注入测试、接口测试、基于需求的测试。这里可以从测试类型的角度,将测试归纳为静态分析测试和动态分析测试。
1.2.1 静态分析:
静态分析是指不用执行程序的测试,对程序文件进行跟踪,即可以在运行程序之前的早期阶段检测分析。它主要采用代码走查、技术评审、代码审查等方式对软件单元或者软件产品进行测试。验证方法有走查、结对编程、检查、控制流分析、数据流分析、静态代码分析,主要集中在软件单元验证和软件集成验证活动中。其中静态代码分析最为主要,其主要目的是检查代码编写是否符合特定的编程规则。对于大部分车辆控制器代码而言,静态代码分析,即C代码静态分析(如果基于模型开发,则是自动生成的代码),主要是保证代码满足MISRA C(Motor Industry Software Reliability Association, 汽车工业软件可靠性协会)相关的要求。
静态代码分析一般可以直接采用自动化检测软件,例如SIMULINK, Model Advisor; Vector, VectorCAST; Perforce, Helix QAC等,通过配置代码检测规则,然后导入源文件进行自动化分析,如果不满足相关要求,则需要对代码进行修改直至满足为止。最终输出静态代码分析报告给予功能安全软件设计验证提供依据。
1.2.2 动态分析:
动态分析是指测试程序的动态形式,对运行着的程序进行跟踪,通过观察程序运行的实际结果来发现错误和缺陷。它包括的验证方法有:基于需求的验证、接口测试、背靠背测试、故障注入测试等,主要集中在软件集成验证和嵌入式软件验证活动中。动态分析在测试环节具体现如下图:
图六、动态分析测试
在给出的验证方法中,测试占据很大部分,如何进行测试,更需要关注具体的测试用例,它规定测试的测试方法、测试环境、测试设备和工具、测试步骤、判断准则、预期结果等等。
2 软件测试用例导出方法
动态分析测试基本上都需要用到测试用例,如何导出测试用例,用尽可能少的测试用例,覆盖尽可能多的测试场景,这个是对功能安全测试工程师一个很高的要求。ISO 26262-6:2018中分别给出了软件单元、集成测试、嵌入式软件测试用例导出方法,这里归类如下图所示:
图七、测试用例导出方法
其中:
等价类的生成和分析,可以基于划分输入输出来识别等价类,为每个等价类选择一个有代表性的测试值;边界值分析主要用于接口测试,接近边界的值、与边界交叉的值及超出范围的值;错误推测法可基于经验学习中收集的数据和专家判断的数据做为依据进行测试;软件操作用例分析可以包括现场软件更新或者嵌入式软件在不同操作模式下的安全相关行为分析,例如,启动、诊断、降级、断电(进入睡眠状态)、通电(唤醒)、校准、不同ECU 之间的模式下的安全行为分析。
如下图给出了测试用例的设计示例可供参考:通过变化f_in_cell_voltage的输入数值,观察b_out_charging_enable输出数值的结果:
图八、测试用例示例
3 软件安全测试的完整性
为了评估验证的完整性并提供证据证明已有测试用例已充分实现相应测试目标,必须对测试完整性进行评估,这里就得提到结构覆盖率这个概念。结构覆盖率用于度量我们设计的测试用例在多大程度上可以覆盖我们的代码,测试人员可以创建代码覆盖缺失的测试用例,以增加覆盖率。在ISO 26262-6:2018中对软件单元,集成软件的测试覆盖率提出了相应要求,具体包括:
图九、结构覆盖率在软件验证过程中的要求
可以看出,软件单元层面结构覆盖率多基于语句、分支等最基本的代码组合部分测试,而集成后的软件架构层面的结构覆盖率多基于函数,其层级更高。二者逐步递进,可以有效的评估软件安全测试的完整性和充分性。
其中:
函数覆盖率和调用覆盖率很好理解,分别用来统计代码中所有函数是否被测试用例执行的比例和执行测试用例时每个函数是否被调用的比例。下面重点介绍一下:语句覆盖率、分支覆盖率以及MC/DC(修改/判定条件覆盖率),因为这三个覆盖率最容易被混淆。
3.1 语句覆盖率
语句覆盖率用于统计每个代码语句被测试用例执行的比例,主要从未执行的语句,死代码、未执行的分支三个范围综合考虑的。
示例:
源代码:为一个简单的计算两个数值之和的函数
方案1:
如果A=3,B=9
代码执行如下图所示:黄色标记的是根据输入值后执行的语句,已执行语句=5,语句总是=7,所以语句的覆盖率:5/7=71%。
方案1:
如果A=-3,B=-9
代码执行如下图所示:黄色标记的是根据输入值后执行的语句,已执行语句=6,语句总是=7,所以语句的覆盖率:6/7=85%。
但是总体的来说,所有的未覆盖的语句都被第二种方案所覆盖,如果覆盖到所有测试边界就可以认为覆盖率为100%。
3.2 分支覆盖率
分支覆盖率用于统计每个判定分支被测试用例执行的比例,确保来自每个分支的每个条件至少执行一次。具体指在if,case,for,forever,while等语句中各个分支的执行情况。
这里还是依语句覆盖率示例代码为例,计算两个数值之和的函数对应的控制流程图如下
从流程图可以看出图中给出两个执行路径1-2-3-4和1-2-5-6。所以我们在设计测试用例时需要考虑把两条路径都要覆盖到。
测试用例:
这里的分支覆盖率一定不可以约分。1/2怎么来的呢?1代表当前覆盖了一条语句,2代表这个程序一共有两个分支。
3.3 MC/DC(修改/判定条件覆盖率)
MC/DC(修改/判定条件覆盖率) 用于判断类代码覆盖检测,相对比较难理解。它是对分支测试的进一步补充,要求在一个程序中每一种所有可能的输入和输出至少出现一次,并且要求判定中的每一个条件必须能够独立影响判定输出,简单可以理解在其他条件不变的前提下,仅改变条件中一个值,而使判定结果改变。
示例:
用例:
对于条件A,用例1和用例2,A取值相反,B和C相同,判定结果分别为1和0;
对于条件B,用例1和用例3,B取值相反,A和C相同,判定结果分别为1和0
对于条件C,用例3和用例4,C取值相反,A和B相同,判定结果分别为0和1。
理论上,对于三个输入判定条件(A, B, C),一共存在8种测试Case,为实现MC/DC全覆盖,然而仅仅用了4个案例就实现了MC/DC全覆盖,使用最少的测试用例达到最高的覆盖率,不仅节省了成本还节省了时间。
总之,结构覆盖率不需要一味追求100%,高结构覆盖率并不完全说明代码已经进行高质量充分测试,它只说明哪些代码没有被测试用例有效执行。结构覆盖率测试可以帮我们反推前期测试用例设计是否充分,是否存在盲点,哪些地方需要进行补充,增加测试完整性。结构覆盖率测试并不能解决软件事先没有考虑到的情形及功能不足。
4 软件测试的环境
在进行测试验证时我们还需要考虑测试环境和目标环境之间的差异,以便在后续测试阶段的目标环境中定义额外的测试。不同阶段的测试对应着不同的测试环境,如下图所示:
图十、软件测试环境要求
其中:模型在环测试主要验证基于模型开发的软件单元和产品;电子控制单元网络测试环境主要用于测试功能安全对于网络安全的影响;硬件在环测试主要应用于接口测试验证软件设计是否符合软硬件接口规范。
关于功能安全软件开发阶段-软件测试验证的分享就到这里了,无论是最初的设计还是后期的验证环节,我们需要时刻规范开发过程中的每一个子阶段,才能避免因软件设计而造成的系统性失效。