bigywater.tech
Java · 327

Mutation Testing:测试是否真的能发现错误

覆盖率 86% 的测试集一定可靠吗?变异测试把代码改坏,看测试能不能发现。这篇用 PIT 在同一个类上跑三套测试,看每个存活的变异说明测试漏了什么、哪些其实杀不死。

34% → 83% → 90%
三套测试集的变异得分(29 个变异里杀死 10 / 24 / 26 个)
86%
弱测试集的行覆盖率(同时变异得分只有 34%)
3 个
最强测试集下剩余存活的变异(按代码推理是等价变异)

弱测试集

实测 · 本机执行输出
  [弱] 耗时 1.0 秒
  >> Generated 29 mutations Killed 10 (34%)
  >> Line Coverage (for mutated classes only): 6/7 (86%)
  各状态的变异数: {'SURVIVED': 13, 'KILLED': 10, 'NO_COVERAGE': 6}
  存活 / 未覆盖的变异(类 方法 行 变异器 → 状态):
    行  4  方法 clamp        ConditionalsBoundaryMutator          SURVIVED
    行  4  方法 clamp        ConditionalsBoundaryMutator          SURVIVED
    行  4  方法 clamp        PrimitiveReturnsMutator              NO_COVERAGE
    行  4  方法 clamp        PrimitiveReturnsMutator              NO_COVERAGE
    行  2  方法 discount     ConditionalsBoundaryMutator          SURVIVED
    行  2  方法 discount     ConditionalsBoundaryMutator          SURVIVED
    行  2  方法 discount     IncrementsMutator                    SURVIVED
    行  2  方法 discount     NegateConditionalsMutator            SURVIVED
    行  2  方法 discount     NegateConditionalsMutator            SURVIVED
    行  2  方法 discount     NegateConditionalsMutator            SURVIVED
    行  2  方法 discount     PrimitiveReturnsMutator              SURVIVED
    行  5  方法 grade        ConditionalsBoundaryMutator          SURVIVED
    行  5  方法 grade        ConditionalsBoundaryMutator          NO_COVERAGE
    行  5  方法 grade        NegateConditionalsMutator            NO_COVERAGE
    行  5  方法 grade        EmptyObjectReturnValsMutator         NO_COVERAGE
    行  5  方法 grade        EmptyObjectReturnValsMutator         NO_COVERAGE
    行  3  方法 isAdult      ConditionalsBoundaryMutator          SURVIVED
    行  7  方法 log          VoidMethodCallMutator                SURVIVED
    行  6  方法 sumPositive  ConditionalsBoundaryMutator          SURVIVED

强测试集

实测 · 本机执行输出
  [强] 耗时 1.0 秒
  >> Generated 29 mutations Killed 24 (83%)
  >> Line Coverage (for mutated classes only): 5/7 (71%)
  各状态的变异数: {'SURVIVED': 4, 'KILLED': 24, 'NO_COVERAGE': 1}
    行  4  方法 clamp        ConditionalsBoundaryMutator          SURVIVED
    行  4  方法 clamp        ConditionalsBoundaryMutator          SURVIVED
    行  4  方法 clamp        PrimitiveReturnsMutator              SURVIVED
    行  7  方法 log          VoidMethodCallMutator                NO_COVERAGE
    行  6  方法 sumPositive  ConditionalsBoundaryMutator          SURVIVED

各变异器的结果

实测 · 本机执行输出
    BooleanTrueReturnValsMutator             {'KILLED': 1}
    ConditionalsBoundaryMutator              {'SURVIVED': 7, 'NO_COVERAGE': 1}
    EmptyObjectReturnValsMutator             {'KILLED': 1, 'NO_COVERAGE': 2}
    IncrementsMutator                        {'SURVIVED': 1}
    MathMutator                              {'KILLED': 1}
    NegateConditionalsMutator                {'KILLED': 5, 'SURVIVED': 3, 'NO_COVERAGE': 1}
    PrimitiveReturnsMutator                  {'NO_COVERAGE': 2, 'KILLED': 2, 'SURVIVED': 1}
    VoidMethodCallMutator                    {'SURVIVED': 1}

更强的测试集

实测 · 本机执行输出
  [更强] 耗时 1.0 秒
  >> Generated 29 mutations Killed 26 (90%)
  >> Line Coverage (for mutated classes only): 6/7 (86%)
  各状态的变异数: {'SURVIVED': 3, 'KILLED': 26}
    行  4  方法 clamp        ConditionalsBoundaryMutator          SURVIVED
    行  4  方法 clamp        ConditionalsBoundaryMutator          SURVIVED
    行  6  方法 sumPositive  ConditionalsBoundaryMutator          SURVIVED

分步演示

PIT 变异测试
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;PIT(pitest-command-line)1.17.2 + pitest-junit5-plugin 1.2.1 + JUnit Jupiter 5.11.3;默认变异器组;被测代码是我写的 6 个单行方法的小类,结论不能推广到真实项目
    • 没有验证:Maven / Gradle 插件的使用、更大项目的运行时间(这里每次只要约 1 秒)、STRONGER / ALL 变异器组、增量分析、等价变异的自动识别(文中的等价结论只是我按代码推理,没有证明)、与属性测试结合
    京ICP备2026067502号-1京公网安备11010502063487号