软件单元测试的设计方法
写在前面:
软件单元测试的设计是一个系统化的过程,旨在验证代码的最小可测试部分(通常是函数或方法)是否按预期工作。软件单元测试的设计是确保代码正确性和可靠性的关键步骤。在软件单元测试中,等价类测试是一种很重要的测试设计方法,它通过将输入数据划分为若干个等价类,并从每个等价类中选取代表性的数据进行测试,以提高测试的全面性和效率。
1.等价类划分法
在设计测试用例的时候,我们通常采用等价类划分法。等价类划分法的原理就是将无穷多数据缩减到有限个等价区域中,通过测试等价区域完成穷尽测试。
等价类划分要满足 3 个原则:
1. 互斥性:各个等价类之间应该是互斥的,即没有交集,确保每个输入数据只属于一个等价类。
2. 全面性:所有等价类的并集仍然是原始的输入域,即所有划分的等价类应该覆盖所有可能的输入条件,确保没有遗漏。
3. 以一代全:任意一个等价类中,所有数据相互“等价”,指一组输入数据,这些数据在程序的某个特定功能上被认为是等价的,即它们对程序的执行结果产生的影响是相同的。
等价类分为有效等价类和无效等价类
有效等价类:指那些在正常操作条件下预期程序能够正确处理的输入数据集合,对应有效输入域的数据,用以检测被测系统能否正确完成指定功能。
无效等价类:指那些预期程序无法正确处理或应该拒绝的输入数据集合,对应无效输入域中的数据,用以考察被测系统的容错性。
2.如何划分等价类
那么如何划分等价类呢?
1. 识别输入条件:确定软件的输入条件,包括用户输入、系统参数、外部接口等。
2. 确定输入域:对于数值型输入,确定其取值范围;对于枚举型输入,列出所有可能的枚举值。
3. 划分有效等价类:识别那些符合软件规格说明的输入条件,这些构成了有效等价类。根据业务逻辑和规则,将输入条件分组,每组构成一个有效等价类。
4. 划分无效等价类:识别那些不符合软件规格说明的输入条件,这些构成了无效等价类。考虑软件如何处理错误输入,例如空值、非法字符、超出范围的数值等。
5. 考虑边界值:对于每个等价类,特别是处于边界的等价类,识别边界值。这些值往往是错误发生的高发区。
如输入条件规定了取值范围时,应划分一个有效等价类和两个无效等价类(一个低于范围,一个高于范围)。如果输入条件是一个布尔量,可以划分一个有效等价类(真)和一个无效等价类(假)。当输入数据有多个可能的值时,为每个值划分一个有效等价类,并考虑一个无效等价类来覆盖不符合任何有效值的输入。
例如 Year 变量需满足条件:1920<=year<=2012,范围内数值为有效等价类,<1920 为一个无效等价类,>2012 为一个无效等价类。
为有效等价类设计测试用例:
为无效等价类设计测试用例:
3.边界值分析
在许多情况下,边界值往往是错误容易发生的地方,因此应特别注意边界条件的处理。对于每个等价类,特别是有效等价类,考虑其边界值,并可能将其视为单独的测试用例。
在进行边界值分析的时候,首先要确定边界值。根据需求规格,识别所有可能的边界条件,如最大值、最小值、上界、下界等,确定每个输入或输出参数的边界值。对于数值型数据,通常需要考虑其最大值、最小值和紧邻这些值的几个点。对于非数值型数据(如字符串长度、颜色选择等),也要确定其边界条件。
在设计测试用例的时候,要针对每个边界值,设计相应的测试用例。测试用例应包括正常范围内的值、刚好在边界上的值以及稍微超出边界的值。特别注意那些“刚好”达到边界条件的情况,因为这些问题在实际应用中往往更容易出现。
按照设计的测试用例执行测试,观察系统在边界条件下的行为是否符合预期,是否存在错误、异常或性能下降等问题。
通过遵循这些步骤,可以在软件单元测试中有效地应用等价类测试方法,以确保软件的功能安全性能得到充分验证。
4.软件单元测试环境
在软件单元测试中,常见的测试环境有 SIL、MIL、PIL 和 HIL,它们分别对应不同的测试阶段和目的:
1. MIL(Model in the Loop)模型在环测试
MIL 测试是在模型开发阶段进行的,通过纯软件仿真的形式验证控制模型是否满足功能需求。它通常发生在系统工程师为了验证算法,使用控制算法模型控制被控对象模型的场景下,或者软件工程师做模型级别的集成测试。
2. SIL(Software in the Loop)软件在环测试
SIL 测试是在 MIL 之后进行的,目的是验证自动生成的代码与算法模型的一致性。它使用与 MIL 相同的测试用例,检查自动生成的代码的输出是否与 MIL 阶段的模型输出一致。SIL 测试通过运行系统环境中的车辆模型和虚拟 ECU 中的 I/O 模型来模拟控制器所需的各种传感器信号,并能接收台架传感器的信号和虚拟 ECU 发出的控制信号。
3. PIL(Processor in the Loop)处理器在环测试
PIL 测试是将自动生成的代码编译为目标处理器需要的形式,并下载到目标处理器上运行,以防止编译过程引入新的错误。PIL 测试也是等效性测试,其方式与 SIL 类似,不同之处是编译好的算法运行在目标处理器上,而 SIL 测试是在算法开发环境进行的。
4. HIL(Hardware in the Loop)硬件在环测试
HIL 测试通常在开发出完整的控制器后进行,有时被控对象(如整车)还未完成开发,或者使用真实被控对象进行测试太危险或成本高。在 HIL 测试中,采用真实控制控制器和虚拟被控对象,以进行全面的功能验证和性能评估。HIL 测试尤其适用于那些对实时响应、外部设备交互要求严苛的嵌入式系统,它能真实反映出软件在实际运行条件下的表现,有效暴露系统在软硬件集成层面可能出现的问题。
这四种测试方法各自的特点如下:
◆ MIL 关注模型层面的功能实现及测试覆盖度情况。
◆ SIL 关注的是生成的代码与模型间的一致性。
◆ PIL 关注的是代码在目标处理器上的运行情况。
◆ HIL 关注的是软硬件集成后的整体系统表现。
5.软件单元测试覆盖率
ISO26262 part6 的表 9—软件单元级别的结构覆盖度指标如下表所示:
1. 语句覆盖:它关注的是程序中每个可执行语句是否至少被执行过一次。这是代码覆盖率分析中最基本的形式,其目的是确保测试用例能够覆盖到程序中的所有代码行。
由于其简单性,语句覆盖通常容易实现和维护。语句覆盖并不保证程序的逻辑路径被完全测试。例如,它不能保证所有的条件分支都被测试,或者所有的循环边界都被触及。为了达到 100%的语句覆盖,测试用例需要设计得能够触发程序中的每一个语句。语句覆盖并不保证程序的质量,因为即使所有语句都被执行,也可能存在逻辑错误未被发现。
例:
2. 分支覆盖:它要求测试用例能够覆盖程序中所有可能的分支路径。分支覆盖关注的是程序中的控制流,特别是条件语句(如 if, else, switch 等)的真假分支是否都被执行过至少一次。
分支覆盖是路径覆盖的一个子集,它确保了程序中的每个分支都被测试。通过确保每个分支都被测试,分支覆盖有助于发现那些只在特定条件下才会触发的缺陷。为了达到分支覆盖,测试用例需要设计得能够触发每个分支的真假条件。分支覆盖比语句覆盖更严格,因为它要求测试每个条件的真假分支。分支覆盖并不保证所有可能的执行路径都被测试,特别是当存在多个条件时。
3. MC/DC 覆盖:要求测试用例能够覆盖程序中所有可能的条件组合,并且每个条件都能独立影响决策的结果。这是一种高级的白盒测试技术,主要用于验证软件中的逻辑条件和决策路径。
MC/DC 要求每个条件在决策中都能独立地影响决策的结果。这意味着,当改变一个条件的值时,决策的结果也会随之改变,而其他条件保持不变。MC/DC 覆盖了所有条件的所有可能结果,以及每个决策的所有可能结果。实现 MC/DC 可能需要更多的测试用例,因为每个条件都需要独立地影响决策的结果。
MC/DC 与其他覆盖标准的区别:
与语句覆盖的区别:语句覆盖关注于代码的行,而 MC/DC 关注于条件和决策的独立影响。
与分支覆盖的区别:
分支覆盖要求测试每个分支的真假路径,而 MC/DC 进一步要求每个条件都能独立地影响决策的结果。
让我们通过一个具体的 C 语言函数例子来展示如何为语句覆盖、分支覆盖和 MC/DC 覆盖设计测试用例。假设我们有以下函数:
语句覆盖测试用例:
语句覆盖要求覆盖函数中的每个语句至少一次。我们需要以下测试用例:
1. get_value(1,1)——覆盖语句 1(x 和 y 都大于 0)。
2. get_value(1,-1)——覆盖语句 2(x 大于 0,y 小于等于 0)。
3. get_value(-1,1)——覆盖语句 3(x 小于等于 0,y 大于 0)。
分支覆盖测试用例:
分支覆盖要求覆盖每个条件的每个可能分支。我们需要以下测试用例:
1. get_value(1,1)——条件 1 真,条件 2 真。
2. get_value(1,-1)——条件 1 真,条件 2 假。
3. get_value(-1,1)——条件 1 假,条件 2 不可达(因为我们已经知道 x 小于等于 0,所以不会检查 y)。
4. get_value(-1,-1)——条件 1 假,条件 2 假。
MC/DC 覆盖测试用例:
MC/DC 覆盖要求每个条件在决策中都能独立地影响决策的结果。我们需要以下测试用例:
1. get_value(1,1)——条件 1 真,条件 2 真,覆盖语句 1。
2. get_value(1,-1)——条件 1 真,条件 2 假,覆盖语句 2。
3. get_value(-1,1)——条件 1 假,条件 2 不可达,覆盖语句 3。
4. get_value(-1,-1)——条件 1 假,条件 2 假,再次覆盖语句 3,但这次是为了证明条件 1 的独立影响。
为了满足 MC/DC 的要求,我们需要确保改变任何一个条件的值都能独立地影响函数的返回值。这意味着我们需要额外的测试用例来证明条件 1 和条件 2 可以独立地影响决策:
5. get_value(2, 0)——条件 1 真,条件 2 真,但 y 的值改变不影响决策,因为 x 大于 0。
6. get_value(0, 1)——条件 1 假,条件 2 真,但 x 的值改变不影响决策,因为 x 不大于 0。
通过这些测试用例,我们可以看到 MC/DC 覆盖比语句覆盖和分支覆盖更为严格,它要求每个条件都能独立地影响决策的结果。这有助于发现那些可能在条件组合中被隐藏的错误。
许多现代的集成开发环境(IDE)和测试工具都支持语句覆盖和分支覆盖,可以自动收集和报告覆盖率数据。一些自动化测试工具可以辅助实现 MC/DC,通过分析代码逻辑并生成相应的测试用例。
END
作者:边俊
文章来源:sasetech
推荐阅读
- 如何考虑芯片的功能安全设计
- 万字长文解读汽车信息安全框架
- 汽车网络安全 -- IDPS 如何帮助 OEM 保证车辆全生命周期的信息安全
- 驾驶行为谱系及反常驾驶行为建模
- 高效信息管理模块:支持自动驾驶安全的数据库解决方案
更多物联网安全,PSA 等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA 技术交流群,请备注研究方向。