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报告可了解变异体详情:

mutations

默认激活的变异器包括:

  • 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项目中获取。


原始标题:Mutation Testing with PITest