Rust 测试太多,哪些真的冗余?用 cargo-test-prune 看变异证据
AI 编码工具让补回归测试变得容易,时间久了,测试套件也可能堆积出许多相似用例。删掉“看起来重复”的测试很危险:两个测试断言相同代码,却可能分别保护不同边界。Rust 工具 cargo-test-prune 尝试把问题变成可检查的实验:它运行测试与代码变异,比较每个测试实际发现了哪些变异,再提出一个保留集合。它报告建议,不会替你删除测试。
它比较的是“观察到的检测能力”
变异测试会对代码做小幅、刻意的修改,例如把 > 0 改成 >= 0,然后观察哪些测试失败。cargo-test-prune 借助 cargo-mutants 生成变异,逐个运行测试,并寻找能覆盖候选测试所发现变异的其他测试。若多个测试在本次实验里发现同一组变异,其中一些便可能成为精简候选。
安装需要 Rust 稳定版工具链,以及 cargo-mutants:
cargo install cargo-mutants --locked cargo install cargo-test-prune --locked # 在目标 Rust 项目的工作区中运行 cargo test-prune analyze
默认分析整个 workspace,即便命令从某个成员包目录启动。想缩小范围,可用 -p 指定包;也能传 manifest 路径和 feature:
cargo test-prune analyze -p parser --max-mutants 100 cargo test-prune analyze --manifest-path ../parser/Cargo.toml --features serde
--max-mutants 100 把实验限制在最多 100 个变异上。README 指出,默认选取按生成顺序排列的前 50 个,因此这是有界实验,并非随机代表样本。报告会呈现已选和总变异数,阅读结论时应把这个范围一并考虑。
先读报告,再决定是否固定测试
命令会把观测数据和建议写入 test-prune.json,可用 --output 指定路径。对重要需求,可以用 --keep 固定必须保留的测试:
cargo test-prune analyze --keep negative --keep backward_compatibility cargo test-prune report test-prune.json --keep negative cargo test-prune report test-prune.json --weight time
--keep 可重复使用,匹配不到测试会报错。report 可以重新计算并展示建议,不会改写原 JSON;重放报告时需要再次提供 pin。默认每个测试权重相同,--weight time 则依据基线运行耗时加权,但其中包含 Cargo 启动开销,时间结果可能波动。
仓库内置的示例给出了重要提醒:五个变异中,工具建议保留两个活动测试覆盖全部五个;一个只抓到 > 0 与 >= 0 差异的 zero 测试仍必须保留。另一个负数输入测试在这组变异中可能成为候选,但这不代表它的规格价值可以忽略。测试是否有意义,不能只由当前变异集合决定。
证据边界比“删几个”更重要
该工具按每项测试分别执行,并在未改变的副本上运行实验。失败、超时、崩溃或无法识别的基线会中止分析;超时不会算作测试杀死变异。构建失败属于不可行变异,构建超时和不完整执行会从集合覆盖范围中排除。被忽略、观测不确定或未发现变异的测试会保留。
因此结果表达的是“在已选变异和成功观测下,哪些测试表现出重叠”,不是未来缺陷检测能力的证明。README 还明确指出:项目代码会以当前用户权限编译和运行,临时副本只隔离变异修改,并非安全沙箱;实验串行执行,成本大致随变异数乘以测试数增长。
范围也有限:支持标准 libtest 单元及集成测试,不分析 doctest、benchmark、自定义 harness 或 nextest。外部路径依赖、工作区外源码和符号链接等也不受支持。它不会缓存、并行执行、生成删除补丁或自动编辑源文件。若项目依赖 Git 信息或父目录配置,临时副本可能还需要适配。
实际落地时,还可以先把变异预算设小,观察运行时间和报告质量,再逐步扩大;若报告中的候选涉及异常、权限、边界值或兼容性,就应优先人工保留或进一步补充变异场景。对大型 workspace,可从变化频繁且测试成本高的包开始,避免一次性把长时间实验塞进每个 PR。稳定后再根据团队的 CI 时限决定是否运行完整分析,并保存 JSON 作为可追溯的实验记录。每次依赖升级或功能变更后也应重新评估,不能把一次运行形成的精简建议长期视为真理。
较稳妥的做法是先在小包或代表性目录跑有限变异,审阅每个候选测试及其规格,再把关键边界测试用 --keep 固定。将结果当作代码审查线索,而不是自动清理命令:变异测试能补充覆盖分析,却无法替团队判断业务要求、测试可读性和未来维护价值。