引言

通常在软件测试过程中,我们会关注测试用例的代码覆盖率。理想情况下,测试用例应该能够覆盖所有业务代码的函数、方法和模块,从而实现 100% 的代码覆盖率。然而,达到理想目标并不意味着测试用例已经健全。接下来,笔者将向您介绍一种称为 “变异测试” 的方法,它可以帮助我们实现目标覆盖率并补充测试用例。

变异测试 (Mutation testing)

官方解释

通俗地讲,变异测试会在程序编译或运行时产生微小的突变(即 “变异体”),理想的测试用例能够检测出突变体带来的程序行为异常。如果一个突变体能被测试用例发现该错误,则称其被消灭(即 “killed”),表明该测试用例是有效的。反之,如果程序行为变化无法被测试用例捕获,则称突变体存活(即 “survived”),表明该测试用例是无效的,需要补充相应的突变测试用例。

变异测试 with PITest

PITest 和 Jumble 都是 Java 程序中的变异测试工具,它们的作用是通过对程序代码进行人为修改(即 “变异”)来生成一系列的变异体,然后运行测试用例对这些变异体进行测试,以评估测试用例的质量和覆盖率。相比于 Jumble 框架,PITest 有以下优点:
•独立的变异测试框架
•支持多种语言
•生成的变异体数量相对较多,因此 PITest 的覆盖率更高
•会自动为每个测试用例生成变异体
基于以上优点和实际需求选择使用 PITest 框架。以下介绍使用 PITest 框架的过程。

1.maven 引入 PIT 插件

2.被测代码

3.单测用例

4.在被测代码执行单元测试,确保单元测试用例都是通过的

5.通过 mvn 命令运行 PIT,并生成 mutationCoverage 报告。图中可以发现,代码覆盖度是 90%,但 Mutation 覆盖度仅为 76%。

生成的突变覆盖度报告结合了突变覆盖和线覆盖信息。其中,深绿色表示已经被测试用例覆盖的突变体,深粉色表示未被测试用例覆盖的突变体;浅粉色表示未被测试用例覆盖的代码行,浅绿色表示已被测试用例覆盖的代码行。

举例分析突变报告内容,源代码中红箭头所指的第 12 行对应于产生突变的红箭头指向的第 12 行。该行代码由 “changed conditional boundary”(即条件边界变更)引起了变异,但测试用例没有捕获到这个突变,因此该突变被标记为 “survived”。

6.为了捕获这个突变,需要添加新的测试用例来覆盖边界条件,以达到让测试用例将突变杀掉(即 “killed”)的目的。

7.再次运行 PIT,并生成新的突变覆盖度报告。补充的测试用例将突变杀掉(即 “killed”)。

变异类型

通过上面的例子,可以是有多种变异类型的。下面是常见的变异测试类型
•条件边界变异(Conditionals Boundary Mutator)
对关系运算 (<, <=, >, >=) 进行变异,例如把 “<” 突变成 “<=”
•反向条件变异(Negate Conditionals Mutator)
对关系运算 (==, !=, <=, >=, <, >) 进行变异,例如把"=="变成"!="
•增量运算变异(Increments Mutator)
对递增或者递减的运算(++, --)进行变异,例如把 “++” 变成 “--”

如果想单独测试某一种或某几种变异类型,可以在 pom 文件中 configuration 属性添加相关变异条件,例如

小结

变异测试属于白盒测试范畴,需要对每个代码变异反复运行测试。如果单元测试已经做得比较完备,那么变异测试能够发挥其最大的价值。
总之,变异测试有助于评估测试用例的质量,测试人员设计的测试用例杀掉的变异体越多,说明其设计的测试用例质量越高。


↙↙↙阅读原文可查看相关链接,并与作者交流