2. Maven依赖
在Maven依赖配置中,我们将使用JUnit运行测试,并通过PITest库在代码中注入变异体(别急,马上解释什么是变异体)。你可以通过这个链接在Maven中央仓库查找最新版本。
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-parent</artifactId>
<version>1.1.10</version>
<type>pom</type>
</dependency>
要让PITest库跑起来,还需要在pom.xml
中添加pitest-maven
插件:
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.1.10</version>
<configuration>
<targetClasses>
<param>com.baeldung.testing.mutation.*</param>
</targetClasses>
<targetTests>
<param>com.baeldung.mutation.test.*</param>
</targetTests>
</configuration>
</plugin>
3. 项目设置
配置好Maven依赖后,来看这个自解释的回文判断函数:
public boolean isPalindrome(String inputString) {
if (inputString.length() == 0) {
return true;
} else {
char firstChar = inputString.charAt(0);
char lastChar = inputString.charAt(inputString.length() - 1);
String mid = inputString.substring(1, inputString.length() - 1);
return (firstChar == lastChar) && isPalindrome(mid);
}
}
再配个简单的JUnit测试验证实现:
@Test
public void whenPalindrom_thenAccept() {
Palindrome palindromeTester = new Palindrome();
assertTrue(palindromeTester.isPalindrome("noon"));
}
目前一切正常,可以成功运行JUnit测试。接下来重点讨论如何用PITest分析代码和变异覆盖率。
4. 代码覆盖率
代码覆盖率在软件行业被广泛使用,用于衡量自动化测试执行了多少执行路径。通过Eclipse IDE的**Eclemma**这类工具,可以基于执行路径测量有效代码覆盖率。
运行TestPalindrome
的覆盖率测试后,轻松达到100%覆盖率——注意isPalindrome
是递归函数,空输入检查自然会被覆盖到。
但代码覆盖率指标有时相当鸡肋,因为100%覆盖率仅表示所有代码行至少执行过一次,完全不能说明测试准确性或用例完整性,这正是变异测试的价值所在。
5. 变异覆盖率
变异测试是一种提升测试充分性和发现代码缺陷的技术。核心思路是动态修改生产代码,观察测试是否失败。
好的测试必须会失败
代码中的每次修改称为变异体(mutant),产生的程序修改版本称为变异(mutation)。
- 如果变异导致测试失败,称为被杀死(killed)
- 如果变异未影响测试行为,称为存活(survived)
现在用Maven运行测试,设置goal为org.pitest:pitest-maven:mutationCoverage
。在target/pit-test/YYYYMMDDHHMI
目录查看HTML报告:
- 100%行覆盖率:7/7
- 63%变异覆盖率:5/8
显然测试覆盖了所有执行路径,所以行覆盖率100%。但PITest注入了8个变异体,其中5个被杀死(导致失败),3个存活。
查看com.baeldung.testing.mutation/Palindrome.java.html
报告可了解变异体详情:
默认激活的变异器包括:
INCREMENTS_MUTATOR
VOID_METHOD_CALL_MUTATOR
RETURN_VALS_MUTATOR
MATH_MUTATOR
NEGATE_CONDITIONALS_MUTATOR
INVERT_NEGS_MUTATOR
CONDITIONALS_BOUNDARY_MUTATOR
更多PITest变异器细节见官方文档。
63%的变异得分暴露了测试用例不足——我们无法验证回文函数能否正确拒绝非回文和近似回文字符串。
6. 提升变异得分
理解变异概念后,需要通过杀死存活变异体来提升得分。以第6行的第一个变异(条件取反)为例:
原始代码:
if (inputString.length() == 0) {
return true;
}
变异后:
if (inputString.length() != 0) {
return true;
}
变异存活是因为测试仍然通过。解决方案是添加新测试,确保引入变异时测试失败。对其他存活变异体同理处理:
@Test
public void whenNotPalindrom_thanReject() {
Palindrome palindromeTester = new Palindrome();
assertFalse(palindromeTester.isPalindrome("box"));
}
@Test
public void whenNearPalindrom_thanReject() {
Palindrome palindromeTester = new Palindrome();
assertFalse(palindromeTester.isPalindrome("neon"));
}
重新运行变异覆盖率测试,检查target目录生成的PITest报告:
- 100%行覆盖率:7/7
- 100%变异覆盖率:8/8
7. PITest测试配置
变异测试可能很吃资源,需要合理配置提升效率。使用targetClasses
标签指定要变异的类列表——实际项目中不可能对所有类做变异测试,太耗时耗资源。
建议明确指定测试中使用的变异器,减少计算资源消耗:
<configuration>
<targetClasses>
<param>com.baeldung.testing.mutation.*</param>
</targetClasses>
<targetTests>
<param>com.baeldung.mutation.test.*</param>
</targetTests>
<mutators>
<mutator>CONSTRUCTOR_CALLS</mutator>
<mutator>VOID_METHOD_CALLS</mutator>
<mutator>RETURN_VALS</mutator>
<mutator>NON_VOID_METHOD_CALLS</mutator>
</mutators>
</configuration>
PITest还提供多种自定义测试策略的选项,例如通过maxMutationsPerClass
限制每类最大变异体数量。更多配置见官方Maven快速指南。
8. 总结
代码覆盖率仍是重要指标,但有时不足以保证代码测试质量。本文展示了如何用PITest库通过变异测试这种更精密的方式确保测试质量,并介绍了如何分析基础PITest报告及提升变异覆盖率。
虽然变异测试能发现代码缺陷,但需谨慎使用——这是个极其耗时耗力的过程。
本文示例代码可在GitHub项目中获取。