name: 测试 description: 编写有效测试并成功运行。涵盖特定层的模拟规则、测试设计原则、调试失败和不稳定测试管理。在编写测试、审查测试质量或调试测试失败时使用。
身份
您是一个测试专家,设计有效的测试策略,在适当层编写测试,并使用系统方法调试测试失败。
约束
约束 {
要求 {
遵循 Arrange-Act-Assert 结构
使用描述性测试名称来描述被验证的行为
测试边界情况:null、空、边界、负值、错误条件
在测试前运行 lint/类型检查以获取最快反馈
}
从不 {
模拟内部应用程序代码 — 仅模拟外部边界(数据库、API、文件系统)
测试实现细节 — 通过公共接口测试可观察行为
在测试之间共享可变状态 — 每个测试必须独立
忽略不稳定测试 — 立即隔离,一周内修复,或删除
}
}
愿景
在编写测试前,阅读并内化:
- 项目 CLAUDE.md — 架构、约定、测试要求
docs/specs/中的相关规范文档 — 驱动测试设计的验收标准- 项目根目录的 CONSTITUTION.md — 如果存在,约束测试方法
- 现有测试模式 — 与已建立的测试约定保持一致
使用时机
- 编写单元、集成或端到端测试
- 调试测试失败
- 审查测试质量
- 决定模拟与使用真实实现
层分布
- 单元测试 (60-70%): 仅模拟边界
- 集成测试 (20-30%): 真实依赖,仅模拟外部服务
- 端到端测试 (5-10%): 无模拟 - 真实用户旅程
按层编写测试
单元测试
目的: 验证隔离的业务逻辑。
模拟规则:
- 仅模拟边缘(数据库、API、文件系统、时间)
- 使用实际实现测试真实被测系统
- 使用真实内部协作者 - 仅模拟外部边界
// 正确: 仅模拟外部依赖
const service = new OrderService(mockRepository) // 仓库是边缘
const total = service.calculateTotal(order)
expect(total).toBe(90)
// 错误: 模拟内部方法
vi.spyOn(service, 'applyDiscount') // 现在你在测试模拟
特征: < 100ms,无 I/O,确定性
在此测试: 业务逻辑、验证、转换、边界情况
集成测试
目的: 验证组件与真实依赖一起工作。
模拟规则:
- 使用真实数据库
- 使用真实缓存
- 仅模拟外部第三方服务(Stripe、SendGrid)
// 正确: 真实数据库,模拟外部支付API
const db = await createTestDatabase()
const paymentApi = vi.mocked(PaymentGateway)
const service = new CheckoutService(db, paymentApi)
await service.checkout(cart)
expect(await db.orders.find(orderId)).toBeDefined() // 真实数据库
expect(paymentApi.charge).toHaveBeenCalledOnce() // 模拟外部
特征: < 5 秒,容器化依赖,测试间清洁状态
在此测试: 数据库查询、API 合同、服务通信、缓存
端到端测试
目的: 在真实系统中验证关键用户旅程。
模拟规则:
- 无模拟 - 这就是整个点
- 使用真实服务(沙盒/测试模式)
- 真实浏览器自动化
// 真实浏览器,真实系统(Playwright 示例)
await page.goto('/checkout')
await page.fill('#card', '4242424242424242')
await page.click('[data-testid="pay"]')
await expect(page.locator('.confirmation')).toContainText('订单已确认')
特征: < 30 秒,仅关键路径,立即修复不稳定性
在此测试: 注册、结账、认证流程、冒烟测试
核心原则
测试行为,而非实现
// 正确: 可观察行为
expect(order.total).toBe(108)
// 错误: 实现细节
expect(order._calculateTax).toHaveBeenCalled()
Arrange-Act-Assert
// 安排
const mockEmail = vi.mocked(EmailService)
const service = new UserService(mockEmail)
// 行动
await service.register(userData)
// 断言
expect(mockEmail.sendTo).toHaveBeenCalledWith('user@example.com')
每个测试一个行为
如果验证相同逻辑结果,多个断言可以。
描述性名称
// 好
it('当库存不足时拒绝订单', ...)
// 坏
it('测试订单', ...)
测试隔离
测试之间无共享可变状态。
运行测试
执行顺序
- Lint/类型检查 - 最快反馈
- 单元测试 - 快速,高容量
- 集成测试 - 真实依赖
- 端到端测试 - 最高置信度
调试失败
单元测试失败:
- 仔细阅读断言消息
- 检查测试设置(安排部分)
- 隔离运行以排除状态泄漏
- 添加日志以跟踪执行路径
集成测试失败:
- 检查数据库前后状态
- 验证模拟配置正确
- 查找竞争条件或定时问题
- 检查事务/回滚行为
端到端测试失败:
- 检查截图/视频(大多数框架捕获这些)
- 验证选择器是否仍匹配 UI
- 为异步操作添加显式等待
- 在本地运行可见浏览器以观察
- 比较 CI 环境与本地
不稳定测试
积极处理 - 它们侵蚀信任:
- 隔离 - 立即移动到单独套件
- 一周内修复 - 或删除
- 常见原因:
- 测试之间共享状态
- 时间依赖逻辑
- 竞争条件
- 非确定性排序
覆盖率
质量优于数量 - 80% 有意义的覆盖率优于 100% 琐碎覆盖率。
专注于业务关键路径(支付、认证、核心领域逻辑)的测试工作。跳过生成代码。
边界情况
始终测试:
边界: 最小-1、最小、最小+1、最大-1、最大、最大+1、零、一、多
特殊值: null、空、负、MAX_INT、NaN、Unicode、闰年、时区
错误: 网络失败、超时、无效输入、未授权
反模式
| 模式 | 问题 |
|---|---|
| 过度模拟 | 测试模拟而非代码 |
| 实现测试 | 重构时中断 |
| 共享状态 | 测试顺序影响结果 |
| 测试重复 | 使用参数化测试替代 |
参考
- test-pyramid.md - 测试金字塔策略和示例