结论先给:变异测试只做增量门禁 + 风险分级,不做全量门禁。全量跑一次 40 分钟对 CI 是不可接受的,但只跑「这次 PR 动过的类」+「核心计费类」,单次能压到 3~8 分钟,完全可以进 main 分支的必跑门禁;其余模块只在 nightly 跑,进看板不卡人。下面是我这边落地了半年的组合,可直接抄。
一、范围切分:怎么把 40 分钟压到 5 分钟
PIT 支持 targetClasses 和 targetTests,这是降时长的关键。我们做的分层:
- PR 增量门禁(必跑):只跑「本次 diff 命中的类」。用
git diff --name-only origin/main...HEAD拿 Java 文件,映射到com.xxx.PricingEngine这种全限定名,喂给 PIT 的--targetClasses。 - 核心类门禁(必跑):维护一个
core-classes.txt(我们约 40 个类,覆盖计价、扣费、对账),每次 PR 无脑跑,不管有没有 diff。 - Nightly 全量(看板):每天凌晨全量跑一次,出趋势图,不卡人。
命令骨架(Maven + pitest-junit5-plugin):
mvn org.pitest:pitest-maven:mutationCoverage \
-DtargetClasses=com.xxx.pricing.* \
-DtargetTests=com.xxx.pricing.*Test \
-Dthreads=$(nproc) \
-DminMutationScore=80 \
-DmutationScoreUnit=PACKAGE \
-DtimestampedReports=false \
-DoutputFormats=XML,HTML
minMutationScore=80 + mutationScoreUnit=PACKAGE 是门禁开关:低于 80% 直接红。核心类单包卡 80%、普通包卡 60%、工具类不设门禁,用 -DexcludedGroups 或 @Tag("no-mutation") 排除。
二、达标线怎么定,别拍脑袋
我踩过最大的坑就是「定 80% 然后团队为凑数字把断言写得更细、测试反而更脆」。建议:
- 按风险分级:核心计价类 ≥80%(这是 P2 复盘命中的),普通业务类 ≥60%,工具类不设门禁只进看板。
- 基线法:达标线本身用「上一版基线 P50 − 1σ」定,别拍绝对值。我们最初是 80%,跑了 3 个月发现核心类稳定在 85~92%,就把线提到 82%——先跑 4 周拿基线,再定线,比一开始拍绝对值稳得多。
- 别把「存活变异 Top5」藏起来:HTML 报告里点进去看哪些变异杀不死,这才是改断言的依据。我们每周 review 一次 Top5,比看数字更有用。
三、工具选型,按语言各一个
- Java:PIT + pitest-junit5-plugin,最成熟,报告 HTML 可读性最好。
- Python:mutmut(
pip install mutmut),mutmut run --paths-to-mutate src/xxx --test-runner "pytest -q tests/xxx",配合mutmut show <id>看存活变异。注意 mutmut 没有原生增量,我们用一个 wrapper 脚本:先git diff --name-only拿文件,再喂--paths-to-mutate。 - TS/JS:stryker-mutator(
npx stryker run),原生支持testFiles和mutate两个字段做范围切分,比 mutmut 好用。
四、和现有覆盖率报告并列展示
Jenkins/GitLab CI 里我们把两个报告并列:
- 覆盖率报告(jacoco/coverage.py)——回答「代码跑没跑到」
- 变异报告(PIT/mutmut/stryker HTML)——回答「断言有没有真校验到」
两边各出一个数字(行覆盖率 % / 变异杀死率 %),在 PR 描述里自动贴一行:
| 指标 | 本 PR | 基线 | 变化 |
| 行覆盖 | 91.2% | 90.8% | +0.4pp |
| 变异杀死率(核心类) | 84.3% | 82.1% | +2.2pp |
团队对着这两个数字 review,比拍「断言够不够严」有依据得多。
五、一个真实踩坑
我们最初把达标线定成「全仓库 ≥75%」,结果工具类(DateUtil、StringUtil 这种)拖了后腿——这些类本身逻辑简单,但 PIT 对边界条件变异特别敏感,怎么改断言都到不了 75%。后来改成「核心类 ≥80%、普通类 ≥60%、工具类不设门禁」,PR 时长从平均 12 分钟降到 4 分钟,且没牺牲核心类的门禁强度。范围切分比阈值调整更重要。
最后留个开放问题:变异测试对「并发/状态机」类 bug 的盲区(比如两个线程同时改 version),你们是怎么补的?我这边目前的思路是给核心类加状态机覆盖 + 并发变异,但感觉还没系统化,欢迎分享。