--- name: tdd-master description: "Use when 用户要实现新功能、修复 bug,或明确要求使用 TDD 方式开发、先写测试时。触发场景:tdd、测试驱动、测试驱动开发、先写测试、红绿重构、单元测试、test driven、TDD开发、用TDD写、测试先行、我要开始开发新功能。" --- # TDD 开发大师 铁律:**没有失败的测试,不写一行生产代码。** 先写代码再补测试的,必须删掉先写的代码重来。 禁止在以下情况下编写任何生产代码: 1. 还没有一个因"正确原因"而失败的测试(RED 阶段未完成) 2. 用户还没有确认接口设计和行为清单(阶段一未完成) 这两条无一例外,"简单功能"也不例外。 ## 核心哲学 ### 竖向切片,而非横向切片 **反模式(横向切片)**:先把所有测试写完,再一口气写所有实现 - 问题:测试套件成为规格书,不是活文档;实现阶段难以获得快速反馈 **正确做法(竖向 tracer bullet 切片)**:每次选一个最小可验证行为,完成 RED→GREEN→REFACTOR 完整循环 - 每个切片都是端到端的最小功能(一个完整行为) - 通过测试可以立即运行并得到结果 ### 测试测行为,而非测实现 ```python # 错误:测试内部实现(脆弱,重构即失效) def test_calls_validate_method(): service = UserService() with patch.object(service, '_validate') as mock: service.create_user(data) mock.assert_called_once() # 正确:测试可观察行为(稳健,重构不影响) def test_create_user_returns_user_id(): service = UserService() user_id = service.create_user({"name": "Alice", "email": "a@example.com"}) assert isinstance(user_id, int) assert user_id > 0 ``` --- ## 工作流 ### 阶段一:规划(获得用户批准前禁止写代码) #### 1.1 接口设计 先设计公共接口,不暴露内部实现细节: ``` 询问用户:这个功能/模块需要提供什么公共接口? 输出:函数/方法签名 + 输入/输出类型 + 前置/后置条件 ``` 加载 `references/testing-principles.md` 检查接口设计原则。 #### 1.2 行为清单 将功能拆解为可测试的行为列表: ``` 待实现的行为: - [ ] 正常路径:[描述] - [ ] 边界条件:[描述] - [ ] 错误路径:[描述] - [ ] 并发场景:[如适用] ``` 每个行为必须是:**独立可测试** + **有明确期望结果** + **最小粒度** #### 1.3 可测试性检查 评估设计是否可测试(加载 `references/testing-principles.md`): - 依赖是否可以被替换(Mock/Stub)? - 是否有隐藏的全局状态? - 是否混合了业务逻辑和 I/O? **将设计展示给用户确认,批准后才开始实现。** --- ### 阶段二:RED-GREEN-REFACTOR 循环 每次选一个行为(从行为清单第一项开始): #### RED 阶段 1. **写最小的失败测试**: - 测试名称描述行为(`test_用户注册成功返回用户ID`) - 只测一个行为 - 使用尽可能真实的代码(避免过度 mock) 2. **强制验证 RED**(不可跳过): 必须在当前消息中实际运行测试命令并贴出失败输出。不得说"测试会失败"而不运行。看到测试失败的输出是进入 GREEN 阶段的唯一门票。 ```bash pytest tests/test_user.py::test_用户注册成功返回用户ID -v ``` 确认:测试因**正确原因**失败(功能未实现),而非因测试代码错误失败 3. **RED 失败则停止**:如果无法让测试变红,说明测试本身有问题,先修复测试 #### GREEN 阶段 1. **写最小的实现**:只写让当前测试通过所需的最少代码 - 可以暂时硬编码(如 `return 42`),只要测试通过 - 禁止超前实现"以后会用到的"功能 2. **强制验证 GREEN**(不可跳过): ```bash pytest tests/test_user.py -v ``` 确认:全部测试通过,包括之前的测试 3. **GREEN 失败则停止**:回到实现代码修复,**不重构,不写新测试** #### REFACTOR 阶段 **只在全绿时重构**,RED 状态下严禁重构。 加载 `references/refactoring-guide.md` 进行安全重构: - 消除重复代码 - 改善命名 - 提取方法(不改变外部行为) 每次重构后立即运行全部测试:全绿则继续,失败则撤销上一步修改。 #### 完成一个行为后 勾选清单,选下一个行为,重复循环。 --- ### 阶段三:深模块设计检查 所有行为实现完成后,加载 `references/deep-modules.md`: - 接口是否足够简单(浅接口是警告信号)? - 是否可以进一步内化复杂性? - 模块边界是否清晰? --- ## Mock 使用原则 加载 `references/mocking-guide.md` 获取详细指引。 **何时使用 Mock**: - 外部 I/O(网络请求、数据库、文件系统) - 时间相关(`datetime.now()`、`time.sleep()`) - 随机数 - 第三方付费 API **何时不用 Mock**: - 自己写的代码(测试真实集成) - 简单的值对象 - 不涉及副作用的纯函数 --- ## 反模式警告 | 反模式 | 识别特征 | 处理方式 | |--------|----------|----------| | 横向切片 | "先把所有测试写完" | 立刻停止,改为逐行为切片 | | 测试实现细节 | `patch.object` 内部方法 | 重写为测试行为 | | 巨型测试 | 一个测试验证多个行为 | 拆分为多个测试 | | 跳过 RED 验证 | "我知道测试会失败" | 必须执行,有时会有惊喜 | | GREEN 阶段重构 | 测试刚过就大改 | 先让所有测试通过再重构 | | 过度 Mock | Mock 了自己写的类 | 测试真实集成 | | 无意义测试名 | `test_it_works` | 改为行为描述 | --- ## 警告:当你想跳过 TDD 流程时 遇到以下想法,立刻停下——这些是你即将违反铁律的信号: | 借口 | 现实 | |------|------| | "这个功能太简单,不需要先写测试" | 简单功能的边界条件往往出错。TDD 的价值在设计接口,不只是测试。 | | "先写代码,写完后补测试效果一样" | 补测试 = 测"代码做了什么",不是测"代码应该做什么"。本质不同。 | | "我已经在心里验证过了,测试只是形式" | 心理验证不算验证。必须运行命令并看到红色输出。 | | "用户在等,没时间写测试" | 没有测试的代码往往需要返工。TDD 总体耗时更短。 | | "这是修复 bug,不需要走 TDD" | 修 bug 时先写能复现 bug 的测试,是最重要的 TDD 应用场景之一。 | | "我只是稍微改了一点现有代码" | "稍微改" 没有测试保护 = 引入回归 bug 的标准路径。 | | "RED 阶段测试肯定会失败,没必要运行" | "肯定会失败"的测试有时不会失败——这意味着测试本身有问题。必须运行。 | **这些借口的本质都是同一件事:你在合理化绕过 TDD。删掉先写的代码,重来。** ## 参考资源 - `references/testing-principles.md` — 测试原则与可测试性设计 - `references/mocking-guide.md` — Mock 使用指南 - `references/refactoring-guide.md` — 安全重构技术 - `references/deep-modules.md` — 深模块设计原则