mcpField
A
ai-qa-review

ai-qa-review

github.com/petrkindlmann测试与质量2221.2k 次安装6 天前更新
代码仓库

关于 ai-qa-review

ai-qa-review 是面向代码审查的 QA 技能,用于检查测试代码质量、应用代码的可测试性和覆盖缺口。它按测试异味分类问题,并要求审查意见指出依据、影响和修复方向。

主要功能

  1. 01审查测试异味与断言质量
  2. 02分析应用代码的可测试性
  3. 03发现边界与错误场景缺口
  4. 04针对 PR 或整个测试套件审查
  5. 05提出 lint 与 mutation 门禁建议

适用场景

  1. 01审查 PR 中新增或修改的测试
  2. 02排查整套测试中的系统性问题
  3. 03分析难以测试的应用代码设计

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/petrkindlmann/qa-skills --skill ai-qa-review

安装后在 Claude Code 中输入/ai-qa-review确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
mutation-testing.md
smell-examples.md
testability-refactors.md

详细介绍

面向 QA 的代码审查:检测测试异味、分析应用代码的可测试性,并找出覆盖缺口。断言 `toBe(true)` 的测试和只输入正常路径数据、覆盖率高达 95% 的测试套件看起来都很理想,却都可能掩盖缺陷——本技能会指出异味、标注代码行,并将“看起来没问题”转化为变异分数门槛。

开始前: 检查项目根目录中是否存在 .agents/qa-project-context.md。该文件包含测试框架约定、命名模式和项目特定的质量标准,可用于调整审查反馈。


快速路径

有三种不同的入口。选择对应行,然后跳转到指定章节。

情况路径跳转到
PR 中有更改过的测试文件运行更改过的文件,为其评分,并根据 PR 检查清单检查差异验证 → PR 审查清单
整个测试套件需要健康检查量化、抽样、找出 3-5 个系统性异味,并提出 lint/变异测试门槛批量审计流程
应用代码遇到“为什么这么难测试?”标出 DI/副作用/纯函数/接口问题,并提供前后对照可测试性分析

三条路径共用同一套异味分类(以下六类)和相同的验证命令。


探索问题

首先,阅读 .agents/qa-project-context.md(如果存在),并跳过其中已有答案的问题。

  1. **审查范围:**审查测试代码的质量、应用代码的可测试性,还是两者都审查?不同范围对应不同的快速路径。
  2. **框架约定:**使用什么测试框架(Jest、Vitest、Playwright、pytest)?约定各不相同——例如 describe/it 嵌套、测试夹具的使用和断言风格——验证命令也因框架而异。
  3. **PR 审查还是批量审计?**PR 审查只运行并为更改过的文件评分。批量审计则扫描整个测试套件,查找系统性模式。
  4. **现有质量标准:**团队是否有记录在案的测试约定?检查 .eslintrc 测试规则、CONTRIBUTING.md 中的测试指南或测试风格指南。
  5. **已知痛点:**是否有反复出现的不稳定测试、运行缓慢的测试套件或难以理解的失败?这些问题决定了应优先关注哪些异味。

核心原则

  1. **测试代码就是生产代码。**应用同样的质量标准:可读性、可维护性和单一职责。难以阅读的测试代码也难以令人信任。

  2. 审查断言了什么,而不只是执行了什么。覆盖率只能证明某行代码运行过;它无法说明错误值是否会被发现。包含大量 toBeTruthy 断言、覆盖率达到 95% 的测试套件几乎发现不了问题。变异分数(见验证)衡量的是覆盖率无法衡量的内容。

  3. **可测试性审查能防止测试债务。**审查应用代码的可测试性,可以在设计问题迫使人们采用别扭的测试变通方案之前发现问题。代码如果难以测试,通常也难以维护。

  4. **将模式固化,而不只是传递知识。**将反复出现的审查反馈转化为 lint 规则、自定义 ESLint 插件或共享测试夹具。反复出现相同反馈,说明缺少自动化。

  5. **异味是症状,不是定论。**测试异味表示可能存在问题;是否有害取决于具体情境。针对复杂工作流的长测试可能是恰当的。针对边界的重 mock 测试也可能是正确的。

  6. **反馈必须可执行。**每条审查意见都必须说明问题所在、为何重要,以及如何修复。“这个测试不好”无法指导行动。“这个测试使用基于 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?
批量审计流程

全面审计测试套件时:

  1. 量化: 按类型(单元测试/集成测试/E2E)、框架和目录统计测试数量。
  2. 抽样: 审查 10-20% 的测试文件,优先选择规模最大且最近改动过的文件。
  3. 识别模式: 找出样本中最常见的 3-5 种异味。
  4. 确定优先级: 按影响排序:可靠性异味 > 诊断性异味 > 设计异味 > 可读性异味。
  5. 自动化: 对于每种常见异味,判断能否通过 ESLint 规则或变异分数门槛自动发现。
  6. 报告: 记录发现的问题(每个已审查文件一行),包含具体示例、建议的修复方式、严重程度(高/中/低)和预估工作量。

提示词模板

以下是三种借助 AI 进行审查的提示词模式:

  1. 审查测试质量:“检查此测试文件是否存在可读性、可靠性、诊断性、设计、AI 生成和覆盖率方面的异味。对于每个问题:指出异味名称和所在行,说明其重要性,并提供修复方案。”

  2. 识别覆盖率缺口:“根据这段应用代码及其现有测试,找出正常流程、错误处理、边界、边缘情况、并发和安全方面缺失的场景。按 P0/P1/P2 排定优先级。”

  3. 提升可测试性:“审查这段应用代码中的硬编码依赖、混杂的副作用、可提取的纯函数和过于宽泛的接口。展示当前代码与重构后的代码。”


反模式

  1. 只用生产代码标准审查测试代码。 测试代码还有其他质量维度(可靠性、诊断能力、覆盖率),生产代码的 Linter 不会检查这些维度。请使用六类异味,而不只是遵循“整洁代码”原则。

  2. 不顾上下文指出每一种异味。 对于复杂状态机,50 行的测试并不代表设置晦涩难懂,而是复杂性所必需。应结合被测行为评估异味。

  3. 建议把所有内容都替换成 Mock。 过度使用 Mock 本身就是一种异味。不要建议 Mock 纯函数、值对象或快速的进程内协作者。Mock 应用于边界:网络、数据库、文件系统、时钟。

  4. 过分关注覆盖率百分比,而不关注覆盖质量。 只有快乐路径测试的 95% 行覆盖率,不如包含错误路径和边界情况的 75% 覆盖率。审查断言的内容,而不只是执行的内容,并以变异分数作为佐证。

  5. 不运行测试就进行审查。 静态分析无法发现运行时问题。运行测试套件(见验证),通过重复运行检查是否存在不稳定问题,并检查执行时间。通过测试但耗时 20 分钟的测试套件存在性能异味。

  6. 只审查一次,之后不再跟进。 测试质量会随时间下降。建立定期审查机制或自动化质量门禁(Lint 规则、变异阈值),以发现回归。


验证

开始发表评论前先运行以下检查——这是该技能要求的运行时检查,现已具体化。在 PR 审查中,将检查范围限定为 PR 修改过的测试文件;批量审计时则运行整个测试套件。

  1. 运行一次测试套件。 审查前确认测试全部通过——绝不要把失败或跳过的测试当作通过来审查。
    • Vitest:npx vitest run <files> · Jest:npx jest <files> · Playwright:npx playwright test <files> · pytest:pytest <files>
  2. 重复运行以发现不稳定问题。 测试一次通过、下一次失败,这就是可靠性异味,而非偶发现象。
    • 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 与随机顺序运行,以发现顺序依赖
  3. 记录每项测试的耗时。 耗时 20 分钟的测试套件存在性能异味。
    • Vitest:--reporter=verbose(输出每项测试的耗时) · Jest:--verbose · Playwright:--reporter=list · pytest:--durations=10
  4. 变异分数——客观的后备依据。 对于难以大规模凭直觉判断的定性异味(封闭 AI 循环、断言薄弱),运行变异测试工具并查看分数。变异分数低于约 60% 的 AI 生成测试无法有效约束实现。完整工具、阈值和配置见 references/mutation-testing.md。
    • JS/TS:npx stryker run · Java:PIT mvn ... mutationCoverage · Rust:cargo mutants · Python:mutmut run

步骤 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 的等待、顺序依赖)与不稳定测试模式有所重叠。

评论

支持 Markdown