| name | testing-best-practices |
| description | 为任何项目(前端或后端)生成专业测试策略和高质量测试代码。当用户描述功能、UI、验收标准,或要求编写测试、创建测试用例、添加测试覆盖、验证功能时,使用此技能。当用户分享需求并希望通过测试验证,或粘贴代码问'该如何测试'时,也触发此技能。即使用户未明确说'测试',只要意图明显是验证行为或质量保障,就应使用此技能。 |
Test Guide
语言要求
所有输出内容必须用中文:测试计划、测试代码注释(JSDoc、行内)、describe/it 名称描述、E2E 脚本注释、E2E 报告、与用户的对话回复。代码标识符(函数名、变量名)保持英文。
SKILL.md 维护规则
每次修改 SKILL.md 后,必须立即同步更新 INTRODUCTION.md(面向人类的中文导航文档),不允许两者不同步。
核心哲学
忠实复现真实用户工作流,可靠验证每个业务场景。
测试金字塔:策略的核心
╱╲
╱E2E╲ ← 10%:昂贵、慢
╱──────╲
╱Integration╲ ← 20%:中等成本
╱──────────────╲
╱ Unit Tests ╲ ← 70%:便宜、快
核心原则:大量单元测试 + 适量集成测试 + 少量 E2E 测试。一个 E2E 测试的成本可能是单元测试的 10 倍以上,细节验证交给低层测试,E2E 只覆盖关键用户旅程。
测试文件组织原则
单元测试 & 集成测试:Co-location(强制)
测试文件必须与被测源码放在同一目录,便于定位和维护。
src/features/userProfile/
├── utils/
│ ├── formatters.ts
│ └── formatters.test.ts # ✅ 单元测试与源码并列
├── components/
│ └── ProfileCard/
│ ├── index.tsx
│ └── index.integration.test.ts # ✅ 集成测试与源码并列
禁止:将测试文件放在远离源码的集中测试目录(如 __tests__/integration/)。
E2E 测试:集中目录
E2E 测试涉及多模块、多页面,需要共享资源(截图、报告、认证状态),因此采用集中目录:
tests/e2e/
├── scripts/ # E2E 脚本
├── screenshots/ # 截图证据
├── reports/ # E2E 报告
└── .auth-state.json # 登录态缓存
通用测试原则
黄金法则:设计瘦测试
测试代码与生产代码不同,要使它变得极其简单、短小、没有抽象、扁平化。一段测试代码需要做到让人一眼就能看出其目的。
我们的大脑空间被主体生产代码充满,无法腾出额外的"大脑空间"存放复杂的东西。测试是友好的助手,投资小回报大,应该像改 HTML 文档一样简单。
AAA 模式
每个测试分为三段:准备(Arrange)、执行(Act)、断言(Assert)。
- 准备:提供上下文的代码,包括构造数据、mocking/stubbing 对象
- 执行:执行测试单元,通常一行代码
- 断言:保证得到的值符合预期,通常一行代码
test('When customer spent more than 500$, should be classified as premium', () => {
const customer = {spent: 505, joined: new Date(), id: 1};
const result = classifier.classify(customer);
expect(result).toBe('premium');
});
三元命名法
测试名称必须包含三部分:
- 被测什么(如 ProductsService.addNewProduct)
- 在什么条件下(如没有传入 price 参数)
- 期望什么结果(如产品状态为待审批)
it('returns correct object')
it('When no price is specified, then the product status is pending approval')
黑盒测试
只测 public 方法,不测内部实现细节。 测试内部逻辑是无意义且浪费时间的——如果 API 返回正确结果,不需要测试它内部如何实现。当测试一个公共方法时,其私有实现也会被隐式测试。
⚠️ 反模式警示:测试真实代码,而非 Mock 对象
这是测试中最严重的认知误区,适用于所有测试类型。
测试的目的是验证「代码是否按预期工作」,而非「测试用例是否通过」。如果测试的是 mock 对象而非真实代码,测试通过毫无意义。
判断标准:如果被测的真实代码有 bug,这个测试会失败吗?
- 会 → ✅ 有效测试
- 不会(因为 mock 返回的是正确的值)→ ❌ 无效测试
Mock 边界原则:只在系统边界(API/网络/第三方服务)mock,不在模块边界 mock。单元测试可以 mock 模块边界,但集成测试和 E2E 必须让真实代码执行。
以上原则适用于所有测试类型(单元、集成、E2E)。详细测试策略见对应参考文档。
Workflow
用户输入(AC / 功能 / UI / 代码)
│
▼
Phase 0: 上下文探测 → 读 package.json、配置文件、已有测试,识别框架 / 约定 / 目录结构
│
▼
Phase 1: 测试计划 → 解析输入、三层扫描、决定测试类型、加载参考文档、输出计划 → 用户确认
│
▼
Phase 2A: 单元测试实现 → 按模块拆分文件、写入代码、执行验证 → 【关卡确认】
│
▼
Phase 2B: 集成测试实现 → 分析模块协作、写入代码、执行验证 → 【关卡确认】
│
▼
Phase 2C: E2E 测试实现 → Mock 数据清单、写脚本、agent-browser 实跑、截图 → 【关卡确认】
│
▼
Phase 3: 持久化 → E2E 脚本、截图、报告入仓库,单测文件头内嵌需求元数据
⚠️ 关键规则:Phase 2 分为三个子阶段,每个子阶段完成后必须输出确认,才能进入下一个子阶段。禁止跳过任何一个子阶段。
Phase 0: Context Detection
按顺序探测:
package.json 等包清单 → 识别 Jest/Vitest/Playwright/PyTest 等框架与脚本
jest.config.* / playwright.config.* 等配置文件 → 路径别名、自定义行为
- 2-3 个已有测试文件 → 命名规范、目录结构、mock 方式
- 源码布局 → 决定测试文件放置位置
无法推断时才追问用户(语言/框架/约定)。
冲突处理:
当探测到的项目规范与 Skill 中的最佳实践不一致时,必须询问用户以哪个为准:
检测到项目约定与最佳实践存在差异:
- 项目约定:[具体内容]
- 最佳实践:[具体内容]
请问以哪个为准?
1. 遵循项目约定
2. 采用最佳实践
Phase 1: Test Plan
解析输入(三种形态):
- AC:提取 Given/When/Then,补充隐含错误态 / 边界 / 空态,标注模糊条目
- 功能描述:拆解离散行为,推断 AC,列出推断并向用户确认
- 代码:分析 public API、分支、错误处理、需 mock 的依赖
强制三层扫描(不可跳过)
在输出任何计划之前,必须对需求做一次完整的三层扫描,每一层都要主动寻找测试机会,不能因为已找到单元测试机会就停止扫描:
第一层:单元测试机会
问:哪些逻辑是纯函数或无副作用的计算?
- 数据提取 / 格式转换 / 映射函数
- 枚举判断 / 优先级规则 / 边界值计算
- 工具函数、常量逻辑
第二层:集成测试机会
问:哪些业务规则需要多个模块协同才能验证?
- 模块间的数据流(一个模块的输出作为另一个模块的输入)
- 状态管理与副作用的组合(某操作触发多处联动更新)
- 条件分支依赖多个数据源(结果由多方状态共同决定)
- 跨层约定(某种数据格式要求 → 消费方的初始化行为)
第三层:E2E 测试机会
问:哪些行为必须在真实浏览器中才能验证?
- 完整用户操作流程(点击 → 跳转 → 弹窗 → 提交)
- 视觉与布局状态(固定定位、动态提示、禁用样式、条件渲染)
- 跨页面 / 跨路由的数据传递与状态保持
- 第三方 UI 组件的交互(弹出层、筛选联动、滚动行为)
⚠️ E2E 用例数量原则:分支逻辑(如"A 条件展示 X,B 条件展示 Y")属于第一/二层,通过 mock 函数入参覆盖。E2E 只验证最终 UI 渲染结果,每条用户旅程写一条 E2E,不为每种用户状态分别写一条。所有用户状态通过 mock 控制,不依赖特定测试账号。
只有三层都扫描完毕,才能进入"测试类型决策"步骤。如果某一层为空,要在计划中明确说明理由(如"无集成测试:各模块无状态共享"),而不是静默忽略。
测试类型决策表:
| 信号 | 推荐类型 |
|---|
| 纯函数、工具、数据变换 | 单元测试 |
| 带 props/state 的组件 | 单元 + 组件测试 |
| Hook 协同、跨模块数据流 | 集成测试 |
| API 端点、服务方法 | 单元 + 集成测试 |
| 跨页面的用户流、UI 交互 | E2E |
| 校验规则、表单 | 单元 + 集成 |
| 第三方集成 | 集成(mock 边界) |
加载参考文档(按需):
| 决策 | 加载 |
|---|
| 单元测试 | references/unit-testing.md |
| 集成测试 | references/integration-testing.md |
| E2E 测试 | references/e2e-testing.md |
| 复杂 mock、可维护性 | references/test-quality.md |
| 覆盖率、CI、性能 | references/ci-coverage-performance.md |
| 报告模板、Mock 数据清单 | references/report-templates.md |
计划输出模板:见 references/report-templates.md → 测试计划输出模板。
计划输出后问用户:**这个测试计划还有需要调整的地方吗?确认后再开始实现。**未经确认不进入 Phase 2。
Phase 2A: 单元测试实现
核心规则:一个测试文件 = 一个功能模块,禁止把不同模块的测试混在一个文件中。
单元测试文件规范
单元测试文件 = 功能归档 + 回归测试。读者不看源码也能还原功能规格。
文件头 JSDoc:每个单测文件必须包含功能归属、迭代信息、业务规则的 JSDoc 头。
命名规则:
describe:[功能名] 某行为或组件组
it:某条件下 / 某操作时,结果是什么(中文,描述业务场景)
- 期望值中的魔法数字必须加行内注释说明业务含义
详细单元测试指南(AAA 模式、命名规范、JSDoc 模板)见 references/unit-testing.md
单元测试报告
必须输出单元测试报告:Phase 2A 完成后,必须生成并保存单元测试报告。
- 报告位置:
tests/unit/reports/<feature-slug>-unit-<YYYYMMDD>.md
- 报告模板:见
references/report-templates.md → 单元测试报告模板
Phase 2A 完成确认
单元测试全部写入并执行后,必须输出确认才能进入 Phase 2B。确认模板见 references/report-templates.md → Phase 完成确认模板。
Phase 2B: 集成测试实现
⚠️ 禁止跳过:即使 Phase 2A 完成了所有单元测试,也必须执行 Phase 2B。如果 Phase 1 计划中集成测试层为空,需要在此阶段重新确认是否真的没有集成测试机会。
集成测试识别检查点
在开始实现前,再次确认以下信号是否存在:
| 检查点 | 是否存在 | 对应集成测试 |
|---|
| 多个模块协作完成一个业务行为 | ☐ | 模块协作测试 |
| API 调用后数据在组件间流转 | ☐ | 数据流测试 |
| 用户操作触发多个状态更新 | ☐ | 状态联动测试 |
| 条件渲染依赖多个数据源 | ☐ | 条件组合测试 |
集成测试核心原则
Mock 边界原则:集成测试只在系统边界(API/网络/文件系统)mock,不在模块边界 mock。让真实代码执行,验证模块间协作。
详见 references/integration-testing.md → 核心反模式警示。
集成测试报告
必须输出集成测试报告:Phase 2B 完成后,必须生成并保存集成测试报告。
- 报告位置:
tests/integration/reports/<feature-slug>-integration-<YYYYMMDD>.md
- 报告模板:见
references/report-templates.md → 集成测试报告模板
Phase 2B 完成确认
集成测试全部写入并执行后,必须输出确认才能进入 Phase 2C。确认模板(含跳过情形)见 references/report-templates.md → Phase 完成确认模板。
Phase 2C: E2E 测试实现
⚠️ E2E 前置条件:Mock 数据清单
禁止在没有 Mock 数据清单的情况下开始 E2E 执行。
在执行任何 E2E 测试之前,必须先输出 Mock 数据需求文档。详细模板见 references/report-templates.md → E2E Mock 数据清单模板。
为什么必须先准备 Mock 数据
E2E 测试验证的是"在正确数据状态下,用户操作路径是否正常",而非"真实账号是否有正确的数据"。
常见错误:
- ❌ "这个场景需要老用户账号才能测试"
- ❌ "因为没有权限,所以这个 UI 没出现,标记为 BLOCKED"
正确做法:
- ✅ 任意账号登录后,通过 mock 注入所需状态
- ✅ UI 未出现时,先检查 mock 数据是否正确注入,再判断是否为 bug
E2E 执行流程
- 输出 Mock 数据清单(必须)
- 确认 dev server 运行(默认
https://localhost:9000/)
- 写 E2E 脚本文件(
tests/e2e/scripts/<功能名>.sh)
- 通过
run_in_terminal 逐步执行 agent-browser 命令
- 每个关键断言点必须截图
- 执行完毕写报告
⚠️ Mock 数据迭代循环:当 E2E 执行发现 mock 数据不对或不足时,必须重新生成 mock 方案并重试,而不是直接标记 BLOCKED 后放弃。详见 references/e2e-testing.md → Mock 数据迭代循环。
阻塞处理:每个阻塞必须尝试至少 2 种解决方案,阻塞后继续测试其他独立场景。禁止归咎于“账号问题”,必须先检查 mock 数据是否正确。
详细 E2E 测试指南(截图要求、报告模板、阻塞处理流程、Mock 迭代循环)见 references/e2e-testing.md
Phase 2C 完成确认
E2E 测试全部执行后,必须输出确认才能进入 Phase 3。确认模板见 references/report-templates.md → Phase 完成确认模板。
Phase 3: Persistence & Traceability
必须入仓库的产物
| 产物 | 位置 | 用途 |
|---|
| 单元测试文件 | 与源码并列(.test.ts) | 规格 + 回归 |
| 单元测试报告 | tests/unit/reports/<feature>-unit-<YYYYMMDD>.md | 执行结果归档 |
| 集成测试文件 | 与源码并列或 __tests__/integration/ | 协作验证 |
| 集成测试报告 | tests/integration/reports/<feature>-integration-<YYYYMMDD>.md | 协作验证结果 |
| E2E 脚本 | tests/e2e/scripts/<feature-slug>.sh | 可重复执行 |
| E2E 截图 | tests/e2e/screenshots/<YYYYMMDD>/ | 视觉证据 |
| E2E 报告 | tests/e2e/reports/<feature-slug>-e2e-<YYYYMMDD>.md | PASS/FAIL 历史 |
| 认证状态 | tests/e2e/.auth-state.json | 登录态缓存(不入 git) |
语言验证点
Phase 3 结束时,必须验证所有输出为中文:
截图保存流程、报告模板详见 references/e2e-testing.md
全流程自检清单
Phase 1 完成检查
Phase 2A 完成检查
Phase 2B 完成检查
Phase 2C 完成检查
Phase 3 完成检查