name: testing-patterns description: “跨语言测试策略与模式。触发词:测试金字塔、单元测试、集成测试、端到端测试、TDD、BDD、测试覆盖率、模拟策略、测试替身、测试隔离。” compatibility: “语言无关模式。框架特定细节见参考资料。” allowed-tools: “Read Write Bash”
测试模式
适用于各种编程语言的通用测试策略与模式。
测试金字塔
/\
/ \ 端到端测试(少量、慢、昂贵)
/ \ - 完整系统测试
/------\ - 真实浏览器/API调用
/ \
/ 集成测试 \ 集成测试(适量)
/ \ - 服务边界
/--------------\ - 数据库、API
/ \
/ 单元测试 \ 单元测试(大量、快速、廉价)
------------------ - 单个函数/类
- 模拟依赖项
测试类型
单元测试
范围: 单个函数/方法/类
速度: 毫秒级
依赖项: 全部模拟
时机: 每次代码变更
覆盖率: 代码库的80%以上
集成测试
范围: 多个组件组合
速度: 秒级
依赖项: 真实数据库,模拟外部API
时机: PR/合并、关键路径
覆盖率: 关键集成点
端到端测试
范围: 完整用户旅程
速度: 分钟级
依赖项: 真实系统(或预发布环境)
时机: 部署前、夜间
覆盖率: 仅关键用户流程
测试命名约定
test_<单元>_<场景>_<预期结果>
示例:
- test_calculate_total_with_discount_returns_reduced_price
- test_user_login_with_invalid_password_returns_401
- test_order_submit_when_out_of_stock_raises_error
准备-执行-断言(AAA)模式
def test_calculate_discount():
# 准备 - 设置测试数据和依赖项
cart = Cart()
cart.add_item(Item(price=100))
discount = Discount(percent=10)
# 执行 - 执行被测试的代码
total = cart.calculate_total(discount)
# 断言 - 验证结果
assert total == 90
测试替身
| 类型 | 目的 | 示例 |
|---|---|---|
| 桩 | 返回预设数据 | stub.get_user.returns(fake_user) |
| 模拟 | 验证交互行为 | mock.send_email.assert_called_once() |
| 间谍 | 记录调用,使用真实实现 | spy.on(service, 'save') |
| 伪造 | 简化的工作实现 | FakeDatabase() 代替真实数据库 |
| 哑元 | 占位符,从不使用 | 用于必需参数的 null 对象 |
测试隔离策略
数据库隔离
选项1:事务回滚(快速)
- 测试前开始事务
- 测试后回滚
选项2:清空表(中等)
- 测试间清除所有数据
选项3:独立数据库(慢)
- 每个测试使用全新数据库
外部服务隔离
选项1:在边界处模拟
- 用模拟对象替换HTTP客户端
选项2:伪造服务器
- WireMock、MSW、VCR录音
选项3:契约测试
- Pact、消费者驱动契约
测试什么
必须测试
- 业务逻辑和计算
- 输入验证和错误处理
- 安全敏感代码(认证、权限)
- 边界情况和边界条件
应该测试
- 集成点(数据库、API)
- 状态转换
- 配置处理
避免测试
- 框架内部实现
- 第三方库行为
- 简单的getter/setter
- 私有实现细节
测试质量检查清单
- [ ] 测试是独立的(无顺序依赖)
- [ ] 测试是确定性的(无不稳定测试)
- [ ] 测试是快速的(单元测试 < 100毫秒,集成测试 < 5秒)
- [ ] 测试名称清晰描述行为
- [ ] 测试覆盖正常路径和错误情况
- [ ] 测试不重复生产逻辑
- [ ] 模拟是最小化的(仅外部边界)
附加资源
./references/tdd-workflow.md- 测试驱动开发流程./references/mocking-strategies.md- 何时以及如何模拟./references/test-data-patterns.md- 夹具、工厂、构建器./references/ci-testing.md- CI/CD流水线中的测试
脚本
./scripts/coverage-check.sh- 运行覆盖率检查,低于阈值则失败