| name | tdd |
| description | 测试驱动开发(TDD)。遵循红-绿-重构循环(Red-Green-Refactor)。适用于:用户要求 TDD、提到"单元测试"、"功能测试"、"红绿重构"、"确保功能正常"、"确保测试通过"或要求每次提交都附带测试时触发。 |
测试驱动开发(TDD)
核心原则
测试验证行为,不验证实现细节。 代码内部可以完全重写,测试不应随之失效。
好的测试通过公共接口描述系统"做什么",而非"怎么做"。一个好的测试读起来像规格说明——"用户可以用有效购物车完成结账"——明确表达了系统能力。
详见 tests.md 中的好坏测试对比,以及 mocking.md 中的 mock 使用规范。
禁止:横向切片
不要先写完所有测试,再写所有实现。 这是"横向切片"——把 RED 阶段理解为"写所有测试",GREEN 阶段理解为"写所有代码"。
这会产生劣质测试:
- 批量写出的测试验证的是"想象的行为",不是"实际行为"
- 测试倾向于验证数据结构和函数签名,而非用户可感知的行为
- 测试对真实变更不敏感——行为破坏时通过,行为正常时失败
正确方式:垂直切片(追踪子弹)。一个测试 → 一个实现 → 循环。
错误(横向):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
正确(垂直):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
...
工作流
1. 规划
编写任何代码前:
不可能测试一切。 聚焦在关键路径和复杂逻辑上,不要追求每个边界条件。
2. 最小单元测试原则
每个测试必须:
- 只测试一个行为
- 使用公共接口,不访问内部状态
- 独立运行,不依赖其他测试的状态
- 快速执行,毫秒级完成
- 命名清晰,名称即规格说明
命名模板:[场景/前置条件]_[操作]_[预期结果]
// 好的命名
test("有效购物车_结账_返回已确认状态")
test("空密码_登录_返回验证错误")
// 差的命名
test("测试结账功能")
test("登录测试")
3. 追踪子弹(第一个测试)
写一个测试,验证系统端到端的一件事:
RED: 为第一个行为写测试 → 测试失败
GREEN: 写最少的代码让测试通过 → 测试通过
4. 增量循环
对每个剩余行为:
RED: 写下一个测试 → 失败
GREEN: 最少代码通过 → 通过
规则:
- 每次只写一个测试
- 只写让当前测试通过的代码
- 不预判未来测试
- 测试只关注可观测行为
5. 重构
所有测试通过后,查找重构点(详见 refactoring.md):
绝不在 RED 状态重构。先到 GREEN,再重构。
每轮循环检查清单
[ ] 测试描述行为,不描述实现
[ ] 测试只使用公共接口
[ ] 测试能在内部重构后继续通过
[ ] 实现代码是让测试通过的最少代码
[ ] 没有添加投机性功能
补充参考