SuperAPI AI 工具集

test-suite-curation

测试与质量

查看项目

更新于 10-14 07:29

技能介绍

test-suite-curation 是一套用于审计整个回归测试套件并基于证据进行精简与重构的技能。其目标不是凭直觉删除测试,而是用可验证的数据支撑每一项处置决定。

用途与目标

该技能针对的是测试套件随着时间推移不断膨胀、其中混有冗余、过时与低价值测试的情况。它提供一套完整的方法,用于发现哪些测试真正在贡献覆盖率、哪些测试只是重复了他人的工作、哪些测试可以安全移除,以及如何重组测试层级。

主要能力

逐测试覆盖率指纹(Coverage Fingerprinting)

该技能的核心做法是使用逐测试(动态)上下文,而非单一的合并 --cov 报告。以 pytest 为例:

pytest --cov=src --cov-branch --cov-context=test

--cov-context=test 会让 coverage.py 在每个测试前后调用 switch_context(),为每一行标记是哪个测试运行了它;--cov-branch 则区分走 if 分支与走 else 分支的测试,避免它们被当作覆盖了「同一行」。结果写入 .coverage SQLite 数据库。随后从该数据库读取 contexts 表,为每个测试构建指纹:它覆盖的确切 (file, line) 与 (file, branch-arc) 集合。由此可以计算:

  • 独占覆盖的行/分支(只有唯一测试覆盖)——失去该测试就彻底失去这些覆盖率,应受保护。
  • 涵盖关系(subsumption)——测试 A 的覆盖集合是测试 B 的超集,可作为冗余候选(但并非证明)。

覆盖率相等的陷阱

该技能强调:覆盖率相等并不等于冗余。两个测试命中相同的行,可能断言完全不同的内容、使用不同输入、检查不同状态。行/分支覆盖率只说明哪些代码运行过,从不说明检查了什么。唯一能证明一个测试涵盖另一个测试的证据是:幸存者能捕获相同的缺陷——这要通过变异测试来证明,而非覆盖率差异。可用工具包括 Python 的 mutmut(3.6.0+)或 cosmic-ray、JS/TS 的 StrykerJS(9.x)、Java/JVM 的 PIT、Rust 的 cargo-mutants。若 A 杀死了 B 杀死的所有变异体,A 才真正涵盖 B 的缺陷检测。决策流程为:覆盖率相同 → 标记为候选 → 用变异测试确认 → 才提议删除。

近似重复聚类(Near-Duplicate Clustering)

用于找出复制粘贴的测试,而不误伤同文件中的其他测试。它结合两种信号:AST(抽象语法树)相似度——解析每个测试为 AST,归一化标识符名与字面量后比较结构,使用 token/树的相似度度量(如归一化 token shingle 的 Jaccard、AST n-gram 的余弦、或树编辑距离);以及覆盖率画像签名——来自覆盖指纹的覆盖行/分支集合。两个信号都接近的才是强候选。使用可调阈值(如层次聚类,起始约 0.85)输出聚类结果,但从不自动删除,每个聚类都交由人工复核。

挖掘 CI 历史

解析测试结果历史(JUnit XML 归档,或 Datadog Test Optimization、Trunk Flaky Tests、BuildPulse、CircleCI test insights 等平台),为每个测试计算通过率/失败率与翻转率。两类发现:

  • 从不失败的测试:朴素做法是删除任何从未失败的测试,这是错误的。从不失败通常意味着它守护的是低变更、低风险、稳定的代码。处置方式为调查而非删除,需检查被测代码的变更率与风险。
  • 始终不稳定的测试:不稳定性不以单次运行判定,真正的定义是同一 SHA/同一提交上出现不同结果(同一代码既通过又失败)。朴素做法是删除不稳定测试以清理 CI,这也是错误的:不稳定测试可能仍是某条真实路径的唯一覆盖。处置方式为隔离并修复不稳定,绝不为了清理 CI 而删除。可参考 test-reliability 处理单个不稳定测试的运行时隔离与自愈。

精简决策规则

区分三种不同状态并施以不同处置:

  • 冗余(Redundant):被另一测试涵盖——覆盖相同行且幸存者杀死相同变异体。处置:合并或删除,但需变异杀死检查确认涵盖关系,且先隔离。
  • 过时(Obsolete):测试的是已移除的功能/死代码/不再存在的路径。处置:删除——先确认目标确实不存在。
  • 低价值(Low-value):从未失败且琐碎(getter、no-op、无有意义断言)。处置:隔离/转人工复核——低价值不等于零价值。
  • 保留(Keep):独占覆盖某些内容、有正向缺陷检测历史、或守护高风险路径。无条件保留,无论覆盖率是否重叠。

分层(Tiering)

将扁平套件拆分为 smoke/core/extended 分层,而不是按速度排序。分层依据两项证据输入:业务风险/关键路径(来自风险图谱),以及缺陷检测历史(实际捕获过真实 bug 的测试)。执行时间仅作为二级决胜条件,不设定层级。分层以 markers/tags 编码(如 @pytest.mark.smoke / @pytest.mark.extended、Jest/Vitest tag、JUnit category),从而可用 pytest -m smoke 选择层级而无需移动文件。

破坏性安全

主张绝不在单个 PR 中移除整批测试,也不直接 rm 测试文件。流程为:先隔离(标记 skipped/xfail,或移入仍留在仓库中的 quarantine//deprecated/ 套件),需具名审批人(通过 CODEOWNERS 或 PR 指定审查者)签字,定义观察窗口(默认 2 个 sprint / 2 个 release)并监控逃逸缺陷,之后才以小编次删除,每批关联其审计行,并保持可恢复。

审计记录

交付物是逐测试的记录,每个被删除测试一行,包含:测试 id/路径、分类与理由、被谁取代、覆盖率变化(前/后)、审批(审批人、PR/commit SHA、日期)、恢复路径。以提交的 CSV/Markdown 表(如 docs/test-curation-log.md)存放,与删除一同版本化。

使用方式

技能从 .agents/qa-project-context.md 读取项目上下文并跳过已回答的问题,随后澄清:语言/运行器、是否有 CI 测试结果历史及回溯范围、套件规模与耗时、业务风险图/关键路径、删除审批人与 CODEOWNERS、可接受的观察窗口。

适用场景

适用于测试栈为 pytest+coverage.py、Jest/Vitest+Istanbul、Go 或 JUnit 的项目,尤其适合回归套件庞大、需要系统化精简与分层重构的团队。

限制

没有 JUnit/Datadog/Trunk 历史时无法挖掘从不失败或不稳定信号,只能依赖覆盖率与聚类。变异测试与逐测试覆盖率在大套件上可能需分片或耗时较长。删除需人工审批,代理只负责提议。关于具体语言工具版本、安装命令与配置细节,资料中暂未提供完整内容。