关于 ai-qa-review
ai-qa-review 是面向代码审查的 QA 技能,用于检查测试代码质量、应用代码的可测试性和覆盖缺口。它按测试异味分类问题,并要求审查意见指出依据、影响和修复方向。
主要功能
- 01审查测试异味与断言质量
- 02分析应用代码的可测试性
- 03发现边界与错误场景缺口
- 04针对 PR 或整个测试套件审查
- 05提出 lint 与 mutation 门禁建议
适用场景
- 01审查 PR 中新增或修改的测试
- 02排查整套测试中的系统性问题
- 03分析难以测试的应用代码设计
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill ai-qa-review安装后在 Claude Code 中输入/ai-qa-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/mutation-testing.mdsmell-examples.mdtestability-refactors.md详细介绍
开始前: 检查项目根目录中是否存在 .agents/qa-project-context.md。该文件包含测试框架约定、命名模式和项目特定的质量标准,可用于调整审查反馈。
快速路径
有三种不同的入口。选择对应行,然后跳转到指定章节。
| 情况 | 路径 | 跳转到 |
|---|---|---|
| PR 中有更改过的测试文件 | 运行更改过的文件,为其评分,并根据 PR 检查清单检查差异 | 验证 → PR 审查清单 |
| 整个测试套件需要健康检查 | 量化、抽样、找出 3-5 个系统性异味,并提出 lint/变异测试门槛 | 批量审计流程 |
| 应用代码遇到“为什么这么难测试?” | 标出 DI/副作用/纯函数/接口问题,并提供前后对照 | 可测试性分析 |
三条路径共用同一套异味分类(以下六类)和相同的验证命令。
探索问题
首先,阅读 .agents/qa-project-context.md(如果存在),并跳过其中已有答案的问题。
- **审查范围:**审查测试代码的质量、应用代码的可测试性,还是两者都审查?不同范围对应不同的快速路径。
- **框架约定:**使用什么测试框架(Jest、Vitest、Playwright、pytest)?约定各不相同——例如
describe/it嵌套、测试夹具的使用和断言风格——验证命令也因框架而异。 - **PR 审查还是批量审计?**PR 审查只运行并为更改过的文件评分。批量审计则扫描整个测试套件,查找系统性模式。
- **现有质量标准:**团队是否有记录在案的测试约定?检查
.eslintrc测试规则、CONTRIBUTING.md中的测试指南或测试风格指南。 - **已知痛点:**是否有反复出现的不稳定测试、运行缓慢的测试套件或难以理解的失败?这些问题决定了应优先关注哪些异味。
核心原则
-
**测试代码就是生产代码。**应用同样的质量标准:可读性、可维护性和单一职责。难以阅读的测试代码也难以令人信任。
-
审查断言了什么,而不只是执行了什么。覆盖率只能证明某行代码运行过;它无法说明错误值是否会被发现。包含大量
toBeTruthy断言、覆盖率达到 95% 的测试套件几乎发现不了问题。变异分数(见验证)衡量的是覆盖率无法衡量的内容。 -
**可测试性审查能防止测试债务。**审查应用代码的可测试性,可以在设计问题迫使人们采用别扭的测试变通方案之前发现问题。代码如果难以测试,通常也难以维护。
-
**将模式固化,而不只是传递知识。**将反复出现的审查反馈转化为 lint 规则、自定义 ESLint 插件或共享测试夹具。反复出现相同反馈,说明缺少自动化。
-
**异味是症状,不是定论。**测试异味表示可能存在问题;是否有害取决于具体情境。针对复杂工作流的长测试可能是恰当的。针对边界的重 mock 测试也可能是正确的。
-
**反馈必须可执行。**每条审查意见都必须说明问题所在、为何重要,以及如何修复。“这个测试不好”无法指导行动。“这个测试使用基于 sleep 的等待,导致测试不稳定——请改为显式等待条件”则可以。
测试异味分类
六个维度。每种异味都按其影响的维度分类,并关联到具体的审查行动。所有已编目异味的完整 SMELL/FIX 代码都在 references/smell-examples.md 中——请突出显示行内指引;前后对照代码是关键内容。
可读性异味
让测试难以一眼看懂的问题。
模糊的设置
表现: 用 30 多行代码构造对象,不相关的字段淹没了测试意图。读者无法判断哪些字段与断言有关。
修复: 提取为工厂函数。测试主体中只应出现与测试相关的数据:使用 buildOrder({ items: [buildItem({ weight: 2.5, quantity: 2 })] }),而不是在行内构造完整的用户、产品和订单对象。
审查操作: 要求提取工厂函数。
神秘访客
表现: loadFixture('report.json')——测试依赖读者看不到的外部数据。要理解断言,读者必须打开另一个文件。
修复: 将与测试相关的数据写在测试中,或使用名称清晰的固定数据。读者无需打开其他文件就应能理解测试。
审查操作: 要求将数据写在测试中,或使用描述性固定数据名称。
重复断言
表现: 多个测试以不同的具体程度断言同一行为(toBe('Alice')、toHaveProperty('name')、toBeDefined())。三个测试,却只覆盖一种行为。
审查操作: 要求合并测试。保留最具体的断言。冗余测试会增加维护成本,却不会增强信心。
可靠性异味
导致测试间歇性失败或在意外环境中失败的问题。
基于休眠的等待
表现: 使用 setTimeout、sleep()、waitForTimeout() 进行同步。请参阅 references/smell-examples.md 中的 SMELL/FIX 示例对(将 waitForTimeout 替换为显式的 toBeVisible 等待)。
审查操作: 拒绝。绝不接受基于休眠的等待。要求使用显式等待条件。
顺序依赖
表现: 测试一起运行时通过,单独运行或以不同顺序运行时失败。请参阅 references/smell-examples.md 中的 SMELL/FIX 示例对(每个测试都自行创建前置条件)。
审查操作: 要求隔离数据。每个测试都必须自行创建前置条件。
与外部服务耦合
表现: 测试调用真实的外部 API(支付网关、电子邮件服务提供商、第三方服务)。请参阅 references/smell-examples.md 中的 SMELL/FIX 示例对(模拟服务边界)。
审查操作: 要求在服务边界使用模拟对象或伪造对象。外部调用应放在集成测试或契约测试中,而不是单元测试中。
诊断性异味
让测试失败难以理解和调试的问题。
断言消息不明确
表现: 断言失败时,没有关于预期内容或失败原因的上下文。请参阅 references/smell-examples.md 中的 SMELL/FIX 示例对(将 toBe(true) 替换为更具体的断言,例如 expect(result.errors).toEqual([]),以显示有问题的值)。
审查操作: 要求使用更有诊断价值的断言。无需阅读测试源码,失败消息也应能说明问题。
每个测试有多个失败原因
表现: 单个测试覆盖多个彼此独立的行为。测试失败时,你无法知道是哪个行为出了问题。请参阅 references/smell-examples.md 中的 SMELL/FIX 示例对(将生命周期测试拆分为每个测试只测一种行为)。
审查操作: 要求拆分测试。每个测试都应只有一个失败原因。
设计异味
会增加维护成本的测试架构问题。
条件测试逻辑
表现: 测试主体中包含 if/else、switch、三元运算符或 for 循环。测试中的分支逻辑本身未经测试——你无法判断实际运行了哪些情况。请参阅 references/smell-examples.md 中的 SMELL/FIX 示例对(将带分支的循环改为 it.each)。
审查操作: 要求使用参数化测试(it.each / test.each)。测试中的条件逻辑会掩盖实际验证的情况。
巨型测试夹具
表现形式: beforeEach 或测试夹具为每个测试都设置 20 多个对象,尽管每个测试只会用到其中 2–3 个。关于 SMELL/FIX 示例对(将单体 beforeEach 替换为每个测试内联设置),请参阅 references/smell-examples.md。
评审操作: 要求使用内联设置。将共享设置移到工厂函数中,而不是放在单体 beforeEach 代码块里。
过度模拟
表现形式: 每个协作者都被模拟了,包括简单的值对象和纯函数。关于 SMELL/FIX 示例对(移除对被测函数本身的模拟),请参阅 references/smell-examples.md。
评审操作: 要求移除不必要的模拟。模拟边界,而非内部实现。
AI 生成测试的异味
当测试代码由编码 Agent(Claude Code、Codex、Cursor、Copilot)生成时,异味分类法并无不同,但有几种典型问题反复出现,值得单独检查。
| 异味 | 检测方式 |
|---|---|
| 幻觉定位器 | 在真实页面上运行一次测试。如果定位器始终无法匹配,说明大模型编造了一个不存在的 data-testid。 |
| 虚构导入 | 静态检查每个导入的符号——该文件或包是否真的导出了它?大模型会编造看似合理的 API(@testing-library/something-that-doesnt-exist)。 |
| 通用测试数据 | example.com、[email protected]、Lorem ipsum、John Doe——这些是 Agent 因缺少项目专用工厂而生成的样板数据。请替换为项目的数据工厂。 |
| 封闭的 AI 循环 | 同一 Agent 在同一会话中编写了实现和测试。测试只是描述 Agent 生成的内容,并不能约束它。将 Agent 编写的测试与至少一个由人工编写的边界测试配套使用,或者按照 shift-left-testing 采用 TDD(测试优先)。较低的变异分数(见 验证)是客观的判断依据。 |
| 项目约定偏离 | 页面对象、测试夹具、命名或断言风格与套件其余部分不同。AI 生成的代码很少能开箱即用地符合本地约定。 |
关于首次生成测试的模式和第 7 步评审清单,请交叉链接至 ai-test-generation。对于 AI 系统的评估套件(相当于提示词的 ESLint),将每种工具的 CLI 运行器——promptfoo eval、deepeval test run(Apache 2.0)、Ragas ragas evaluate / experiments(Apache 2.0)——配置为与测试运行器并行的质量门禁。
Promptfoo 归属说明: Promptfoo 已被 OpenAI 收购(于 2026 年 3 月 9 日宣布)。核心部分仍采用 MIT 许可、保持开源且与模型无关;红队功能正并入 OpenAI Frontier。
promptfoo eval仍是正确的质量门禁命令——只需注意,今后的供应商将是 OpenAI。
覆盖率异味
导致验证内容存在缺口的问题。
仅测试成功路径
表现形式: 每个测试都提供有效输入并期望成功。没有测试错误路径。关于 SMELL/FIX 示例对(为折扣计算器添加零值、最大值、负值和边界情况),请参阅 references/smell-examples.md。
评审操作: 要求补充缺失场景。使用 BOUNDARY 框架:边界值、空值/空内容、重复项、顺序、范围限制。
缺少边界情况
表现形式: 测试了“正常”值(5 个项目),却没有测试 0、1、最大值或最大值 + 1。使用 it.each 明确覆盖边界:空集合、单个项目、恰好达到页面大小、超出一个、较大的集合。
评审操作: 要求添加边界测试。每个数值参数、字符串长度和集合大小都有需要测试的边界。
缺少错误/负向情况
表现形式: 没有测试出错时会发生什么——网络故障、无效输入、权限被拒绝、并发修改。
评审操作: 对每个成功路径测试,都要问:“对应的失败模式是什么?”要求为该失败模式编写测试。
可测试性分析
审查应用代码时,评估其结构是否便于测试。每个小节都在 references/testability-refactors.md 中提供了难以测试与便于测试的前后对比示例。
依赖注入
标记直接实例化依赖项的类(例如在方法内调用 new PostgresDatabase()、new StripeClient())。建议使用构造函数注入,以便测试可以替换为模拟对象或伪对象。参见 references/testability-refactors.md。
副作用隔离
标记将纯计算与 I/O(电子邮件、日志记录、分析)混在一起的函数。将计算提取为纯函数,再从有副作用的协调函数中调用。参见 references/testability-refactors.md 中 calcTotal 的前后对比示例。
提取纯函数
查找隐藏在请求处理程序中的验证、转换和业务规则。如果逻辑直接写在 app.post('/api/orders', ...) 中,就无法在不启动 HTTP 服务器的情况下进行单元测试。将其提取为独立函数。参见 references/testability-refactors.md 中 shippingFor 的前后对比示例。
接口隔离
标记依赖宽泛接口(如整个 PrismaClient)但只使用 2-3 个方法的类。建议使用只包含实际所用方法的精简接口,让实现测试替身变得轻而易举。参见 references/testability-refactors.md。
审查工作流
PR 审查清单
对于 PR 中的每个测试文件,按以下步骤系统地检查:
## Test Quality Review
### Readability
- [ ] Can I understand what each test verifies in under 10 seconds?
- [ ] Is setup minimal and test-relevant?
- [ ] Are test names descriptive: "should [behavior] when [condition]"?
### Reliability
- [ ] No sleep/waitForTimeout/setTimeout for synchronization?
- [ ] No shared mutable state between tests?
- [ ] No dependency on test execution order?
- [ ] No calls to real external services?
### Diagnostic Value
- [ ] Will failures produce messages that identify the problem?
- [ ] Does each test have one reason to fail?
- [ ] Are assertions specific (not toBeTruthy/toBeDefined)?
### Design
- [ ] No conditional logic (if/else/switch/for) in test bodies?
- [ ] Fixtures/setup proportional to what each test needs?
- [ ] Mocking limited to external boundaries?
- [ ] Parameterized tests (it.each) used for data-driven scenarios?
### AI-Generated (if applicable)
- [ ] Locators verified against a real page (no hallucinated data-testid)?
- [ ] Imports resolve (no fabricated APIs)?
- [ ] Project data factory used, not [email protected] / John Doe?
### Coverage
- [ ] Happy path AND error/negative paths tested?
- [ ] Boundary values tested (0, 1, max, max+1)?
- [ ] Edge cases: empty, null, duplicate, concurrent?
批量审计流程
全面审计测试套件时:
- 量化: 按类型(单元测试/集成测试/E2E)、框架和目录统计测试数量。
- 抽样: 审查 10-20% 的测试文件,优先选择规模最大且最近改动过的文件。
- 识别模式: 找出样本中最常见的 3-5 种异味。
- 确定优先级: 按影响排序:可靠性异味 > 诊断性异味 > 设计异味 > 可读性异味。
- 自动化: 对于每种常见异味,判断能否通过 ESLint 规则或变异分数门槛自动发现。
- 报告: 记录发现的问题(每个已审查文件一行),包含具体示例、建议的修复方式、严重程度(高/中/低)和预估工作量。
提示词模板
以下是三种借助 AI 进行审查的提示词模式:
-
审查测试质量:“检查此测试文件是否存在可读性、可靠性、诊断性、设计、AI 生成和覆盖率方面的异味。对于每个问题:指出异味名称和所在行,说明其重要性,并提供修复方案。”
-
识别覆盖率缺口:“根据这段应用代码及其现有测试,找出正常流程、错误处理、边界、边缘情况、并发和安全方面缺失的场景。按 P0/P1/P2 排定优先级。”
-
提升可测试性:“审查这段应用代码中的硬编码依赖、混杂的副作用、可提取的纯函数和过于宽泛的接口。展示当前代码与重构后的代码。”
反模式
-
只用生产代码标准审查测试代码。 测试代码还有其他质量维度(可靠性、诊断能力、覆盖率),生产代码的 Linter 不会检查这些维度。请使用六类异味,而不只是遵循“整洁代码”原则。
-
不顾上下文指出每一种异味。 对于复杂状态机,50 行的测试并不代表设置晦涩难懂,而是复杂性所必需。应结合被测行为评估异味。
-
建议把所有内容都替换成 Mock。 过度使用 Mock 本身就是一种异味。不要建议 Mock 纯函数、值对象或快速的进程内协作者。Mock 应用于边界:网络、数据库、文件系统、时钟。
-
过分关注覆盖率百分比,而不关注覆盖质量。 只有快乐路径测试的 95% 行覆盖率,不如包含错误路径和边界情况的 75% 覆盖率。审查断言的内容,而不只是执行的内容,并以变异分数作为佐证。
-
不运行测试就进行审查。 静态分析无法发现运行时问题。运行测试套件(见验证),通过重复运行检查是否存在不稳定问题,并检查执行时间。通过测试但耗时 20 分钟的测试套件存在性能异味。
-
只审查一次,之后不再跟进。 测试质量会随时间下降。建立定期审查机制或自动化质量门禁(Lint 规则、变异阈值),以发现回归。
验证
开始发表评论前先运行以下检查——这是该技能要求的运行时检查,现已具体化。在 PR 审查中,将检查范围限定为 PR 修改过的测试文件;批量审计时则运行整个测试套件。
- 运行一次测试套件。 审查前确认测试全部通过——绝不要把失败或跳过的测试当作通过来审查。
- Vitest:
npx vitest run <files>· Jest:npx jest <files>· Playwright:npx playwright test <files>· pytest:pytest <files>
- Vitest:
- 重复运行以发现不稳定问题。 测试一次通过、下一次失败,这就是可靠性异味,而非偶发现象。
- Vitest:循环运行
npx vitest run --retry=0 <files>,或运行for i in 1 2 3; do npx vitest run <files> || break; done - Jest:
npx jest <files> && npx jest <files> && npx jest <files>(或重复运行jest-circus) - Playwright:
npx playwright test --repeat-each=3 <files> - pytest:
pytest --count=3 <files>(pytest-repeat),或对比pytest -p no:randomly与随机顺序运行,以发现顺序依赖
- Vitest:循环运行
- 记录每项测试的耗时。 耗时 20 分钟的测试套件存在性能异味。
- Vitest:
--reporter=verbose(输出每项测试的耗时) · Jest:--verbose· Playwright:--reporter=list· pytest:--durations=10
- Vitest:
- 变异分数——客观的后备依据。 对于难以大规模凭直觉判断的定性异味(封闭 AI 循环、断言薄弱),运行变异测试工具并查看分数。变异分数低于约 60% 的 AI 生成测试无法有效约束实现。完整工具、阈值和配置见
references/mutation-testing.md。- JS/TS:
npx stryker run· Java:PITmvn ... mutationCoverage· Rust:cargo mutants· Python:mutmut run
- JS/TS:
步骤 1–3 的输出以及变异分数,是报告中每项“可靠性”/“诊断能力”问题的依据。
完成标准
- 发现项报告文件已创建,其中每个已审查文件对应一行,且每行都标有严重程度(high/medium/low)。(文件存在,行数 == 已审查文件数)
- 已涵盖适用于已审查代码的每个代码异味维度:可读性、可靠性、诊断、设计、AI 生成、覆盖率。(报告中包含六个标题;允许标注「N/A」,但不能留空)
- 每项高严重程度的发现都包含可执行的修复步骤(做什么 + 为什么 + 如何修复),而不只是描述问题。
- 已运行验证:测试套件连续至少 3 次全部通过(没有不稳定失败),并记录了每项测试的耗时;对于 AI 生成的测试套件,还记录了变异测试分数。
- 至少有一种反复出现的代码异味被转化为自动化门禁——已提交 ESLint 规则、CI 检查或变异测试分数阈值——或者报告明确说明无需设置此类门禁。
参考文件(位于 references/)
- smell-examples.md — 涵盖可靠性、诊断、设计和覆盖率维度中所有已编目的代码异味的完整 SMELL/FIX 代码示例。
- testability-refactors.md — 四个可测试性小节的难以测试与易于测试的前后对比:依赖注入、副作用隔离、纯函数提取、接口隔离。
- mutation-testing.md — 工具(Stryker/PIT/cargo-mutants/mutmut)、阈值、Stryker 配置,以及 AI 循环 / 弱断言门禁。
相关技能
- unit-testing — 特定框架的模式(Jest、Vitest、pytest),为各框架中「良好」的标准提供参考。
- shift-left-testing — 能在评审前发现测试代码异味的提交前钩子和 IDE 集成。
- coverage-analysis — 解读覆盖率报告,以发现有意义的缺口,而不只是追求百分比目标。
- ai-test-generation — 生成的测试也需要评审。将这些代码异味检查应用于 AI 生成的测试代码。
- test-reliability — 可靠性代码异味(基于 sleep 的等待、顺序依赖)与不稳定测试模式有所重叠。