Community写作与编辑github.com

test-suite-doctor

用于审计、精简和修复臃肿的 Vitest/Jest 测试套件的 Agent 技能与独立 CLI 工具——提供每个测试文件的覆盖率指标、贪心最小化算法、AI 生成垃圾代码检测以及变异测试分数验证。

test-suite-doctor 是什么?

test-suite-doctor 是一个专门针对大规模测试套件(例如 2000 多个测试用例,其中大量为 AI 生成的低质量代码)进行精简的工具。它通过基于实际测量的覆盖率数据(而非主观判断)来指导测试删除,确保在大幅减少测试数量的同时,保留用户指定比例的真实代码覆盖率。该工具支持 Vitest 和 Jest 两大测试框架,工作流程分为五个阶段:收集(收集每个测试文件的独立覆盖率)、最小化(使用贪心算法选择最优测试子集)、人工审核(审查并确认删除列表)、重新生成(为覆盖率缺口补充高质量测试)和验证(确保最终套件达到覆盖率目标且测试通过)。此外,它还能检测并分类 AI 生成的“垃圾”测试模式(如 mock 嵌套 mock、空洞快照等),并支持可选的变异测试验证,确保核心模块的测试质量。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add JoseAntonioNuevo/test-suite-doctor

Installed? Explore more 写作与编辑 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

test-suite-doctor 是做什么的?

将臃肿的测试套件(例如 2000+ 个测试,其中大量是 AI 生成的垃圾代码) 精简为一个小而精的高质量套件,同时保留可配置比例的真实覆盖率—— 基于实际测量,而非猜测。

硬性规则

  1. 先测后判,数据说话。 在 collect 和 minimize 产生计划之前,永远不要提议删除任何测试。仅凭阅读测试代码就判断其“无用”不是证据——一个看起来微不足道的测试可能是某个分支的唯一覆盖。
  2. 计划只是提案,不是最终判决。 覆盖率无法感知 API 契约、有文档记录的回归用例或属性不变性。每个删除候选都需要经过审核(步骤 3)才能删除。
  3. 在分支上操作。 在删除任何内容之前,确保工作树是干净的,并创建一个分支(例如 test-doctor/minimize)。切勿在脏工作树或默认分支上删除测试。
  4. 验证或回滚。 只有 verify 退出码为 0 才算任务完成。如果验证失败,则为报告出的缺口重新生成测试并重新验证——或者恢复已删除的测试。

先决条件检查(请先执行此操作)

  • Node.js ≥ 22,并且 package.json 中使用本地安装的 Vitest 或 Jest( node <skill-root>/dist/cli.mjs collect --help)。
  • 必须安装覆盖率提供程序(Vitest:@vitest/coverage-v8; Jest 内置一个)。如果缺少,主动提议将其安装为 devDependency。
  • 变异测试验证额外需要目标仓库中已配置好 @stryker-mutator/core ——这是可选功能,未经用户同意请勿安装。
  • 开始前测试套件应是绿色的。如果基准运行报告有失败,告知用户并就如何处理它们达成一致后再进行最小化(失败的测试会提供不可靠的覆盖率)。

以下所有命令都在目标仓库根目录下运行;$SKILL 是该技能的目录。 产物会存放在 .test-doctor/ 目录下(建议将其加入 .gitignore)。

工作流程

1. 收集(脚本——始终首先执行)

node "$SKILL/dist/cli.mjs" collect --cwd .

检测测试运行器,先完整运行一次整个套件作为覆盖率基线,然后 单独重新运行每个测试文件,记录覆盖的行数/分支和执行时间。 写入 .test-doctor/report.json。

  • 默认粒度是每个测试文件(按测试隔离是精确的,但会 单独运行每个测试——仅对小型或已精简的套件,或使用 --filter 限定范围时,才提供 --granularity test 选项)。
  • 对于 2000 个测试的套件,这需要一段时间;请在后台运行并报告进度。 认真对待报告中的 collectionErrors——这些单元默认会阻止最小化。 如果用户明确选择 --keep-unmeasured,则每个受影响的单元都是强制保留的,并且计划会被标记为不可信。
  • --filter 会创建一个有意限定了范围的基线:收集、最小化 和验证仅覆盖匹配的测试文件。在每个建议中说明这个范围; 它不能保证整个仓库的覆盖。

2. 最小化(脚本)

node "$SKILL/dist/cli.mjs" minimize --coverage-floor 0.97 --target-count 200

贪心加权选择:重复保留每毫秒运行时覆盖最多新行/分支的单元,直到达到覆盖率下限。写入 .test-doctor/plan.json 和人类可读的 .test-doctor/plan.md,其中包含每个保留和删除的单元的理由。

  • 如果用户未指定,请询问他们的阈值;默认值是 0.97 的行覆盖率下限, 无分支覆盖率下限,无目标计数。--help 列出所有参数。
  • 预先强制保留已知的契约/回归测试: --keep 'contract' --keep 'regression'(基于单元 ID 的正则表达式)。
  • 注意计划的 warnings 数组——例如无法达到下限,或 需要的单元数超过目标计数。将警告逐字传达给用户。

3. 审核(判断——由你完成)

阅读 references/slop-patterns.md,然后审核 plan.md 中的删除列表:

  • 拯救任何验证覆盖率无法感知的行为的测试:公共 API 契约、引用问题/错误 ID 的回归测试、消费者依赖的错误消息措辞、 属性/不变性检查。使用额外的 --keep 模式重新运行 minimize,而不是手动编辑计划。
  • 确认其余的都是安全的删除项;按垃圾模式归类它们,这样用户就能看到为什么每个测试被删除(模式文件中有分类目录)。

然后对照 references/quality-rules.md 审核保留列表: 保留的测试中,看起来有问题的(mock 嵌套 mock、同义反复、空洞快照)会在原地重写—— 它们赢得了覆盖率,但应该以干净的方式赢得。

仅在完成此审核后,才在分支上删除已丢弃的测试文件/用例。 当某个文件中的所有单元都被删除时,删除整个文件;当只有文件中的部分测试被删除时,编辑文件(在测试粒度下)。

4. 重新生成(判断——由你完成)

先运行 verify 一次(预期可能会失败)以获取覆盖率缺口报告,然后 为 verify 列出的受影响最严重的源文件编写新的测试,遵循 references/quality-rules.md:AAA 结构、行为驱动命名、每个测试一个行为、 仅在外部边界使用 mock。一个覆盖真实用户可见行为的强测试优于三个浅层测试。

5. 验证(脚本——作为完成的关卡)

node "$SKILL/dist/cli.mjs" verify --coverage-floor 0.97
# 可选,仅对关键模块启用(较慢):
node "$SKILL/dist/cli.mjs" verify --mutation --mutate "src/billing/**/*.ts" --mutation-floor 80

重新运行套件,要求为绿色,并要求与基线相比的行保留率满足下限。 退出码 0 = 完成;退出码 1 = 修复并重新运行(重新生成更多测试,或恢复删除); 退出码 2 = 环境问题,停止并报告。变异测试是可选的,因其较慢—— 仅在用户认为关键的模块上提供。

版本 2 会为已覆盖的源文件、测试运行器配置、锁文件、运行器 和覆盖率提供程序生成指纹并与基线比对。如果 verify 报告来源偏移, 则重新收集基线。仅在有明确用户同意的情况下使用 --allow-provenance-drift, 并将结果判定报告为不可信。旧的 v1 基线同样需要显式的 --allow-legacy-baseline 逃生舱。

报告

为用户总结:单元数(精简前 → 精简后)、行/分支保留率、运行时间(精简前 → 精简后)、发现的垃圾模式(含计数)、从删除列表中拯救的测试及原因、重新生成的测试以及最终的验证结果。包含 plan.md 的路径以提供完整的审计跟踪。

失败模式

  • 未检测到运行器 / 不明确 → CLI 退出码为 2 并附带指导信息;传入 --runner 或 --cwd(对于 monorepo:在每个包中单独运行)。
  • 收集不完整 → 修复孤立的失败。不要仅仅为了生成一个通过的计划就将它们过滤掉;过滤器会改变声明的基线范围。 --keep-unmeasured 是保守的逃生舱。
  • 重新生成后验证仍然失败 → 在大约 3 次尝试后停止循环; 按文件呈现剩余的缺口,让用户决定是降低下限还是保留更多测试。
  • 开始前套件是红色的 → 不要默默地“修复”无关的失败; 先报告它们并就范围达成一致。

相关技能