tdd
- 信任分
- 88/100
- 兼容 Agent
- 1
- 领域
- 工程开发
- 兼容 Agent
- Claude Code
- 信任分
- 88 / 100 · 社区维护
- 作者 / 版本 / 许可
- @mattpocock · 未声明 license
- 安装命令数
- 1 条
需要注意: 未限定 allowed-tools,默认拥有全部工具权限。
想读作者英文原文? ↓ 滚到正文区切换 · 在 GitHub 查看 ↗
tdd 是「测试驱动开发」的紧凑版纪律:测试必须通过 public interface 验证 behavior,绝不耦合实现细节。代码可以整段重写,测试不该跟着崩——这是 good test 与 bad test 的核心分水岭。
设计思路
作者把 good test 称为 integration-style:跑真实代码路径走 public API,描述「系统能干什么」("user can checkout with valid cart"),refactor 不会让它崩。bad test 则被钉在实现上:mock 内部协作者、测私方法、绕开接口直接查 DB——它们在你 rename 一个内部函数时就崩,但 behavior 根本没变。
反模式:横切(Horizontal Slices)
作者明令禁止「先写完所有测试再写所有实现」——这是把 RED 当「写完所有测试」、把 GREEN 当「写完所有代码」。其结果是:测试在批量阶段测的是想象中的 behavior,不是真实 behavior;测试沦为对「数据结构 / 函数签名形状」的描摹,对真实变化不敏感(behavior 坏了它还过 / behavior 没坏它却挂);你冲在自己的 headlights 前面。
正确做法:垂直 tracer bullets
RED → GREEN: test1 → impl1,RED → GREEN: test2 → impl2……一条测试驱出一条实现,下一条测试响应你刚学到的东西,因为代码刚写完,你最清楚什么 behavior 重要、怎么验证。
工作流
1. Planning:探代码时用项目领域词典,让测试名 + 接口词汇与项目语言一致;尊重要触及区域的 ADR;动手前与用户确认 interface 变化。后续每个 RED→GREEN→REFACTOR 循环都遵照配套文档:tests.md(例子)、mocking.md(mock 的边界)。
适合谁
- 想把测试变成可信赖的回归网而不是 ceremony
- refactor 频繁的代码库,希望测试不在改名时崩
- 团队里有人写「测了 100 个签名」式 test,要校正方向
何时不要用
- 一次性脚本 / 抛弃式 prototype
- 生成代码 / 配置文件——这些走别的验证手段更经济
配套
test-driven-development(同名更长版含 Red-Green-Refactor 详细图与例子)、verification-before-completion(落地前自检)、subagent-driven-development(每条 task 内部走 RED→GREEN)、receiving-code-review(review 反馈时不要乱删测试)。
Test-Driven Development
Philosophy
Core principle: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
Good tests are integration-style: they exercise real code paths through public APIs. They describe what the system does, not how it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.
Bad tests are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
See tests.md for examples and mocking.md for mocking guidelines.
Anti-Pattern: Horizontal Slices
DO NOT write all tests first, then all implementation. This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."
This produces crap tests:
- Tests written in bulk test imagined behavior, not actual behavior
- You end up testing the shape of things (data structures, function signatures) rather than user-facing behavior
- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
- You outrun your headlights, committing to test structure before understanding the implementation
Correct approach: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. Because you just wrote the code, you know exactly what behavior matters and how to verify it.
WRONG (horizontal):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT (vertical):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
...
Workflow
1. Planning
When exploring the codebase, use the project's domain glossary so that test names and interface vocabulary match the project's language, and respect ADRs in the area you're touching.
Before writing any code:
- Confirm with user what interface changes are needed
- Confirm with user which behaviors to test (prioritize)
- Identify opportunities for deep modules (small interface, deep implementation)
- Design interfaces for testability
- List the behaviors to test (not implementation steps)
- Get user approval on the plan
Ask: "What should the public interface look like? Which behaviors are most important to test?"
You can't test everything. Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case.
2. Tracer Bullet
Write ONE test that confirms ONE thing about the system:
RED: Write test for first behavior → test fails
GREEN: Write minimal code to pass → test passes
This is your tracer bullet - proves the path works end-to-end.
3. Incremental Loop
For each remaining behavior:
RED: Write next test → fails
GREEN: Minimal code to pass → passes
Rules:
- One test at a time
- Only enough code to pass current test
- Don't anticipate future tests
- Keep tests focused on observable behavior
4. Refactor
After all tests pass, look for refactor candidates:
- Extract duplication
- Deepen modules (move complexity behind simple interfaces)
- Apply SOLID principles where natural
- Consider what new code reveals about existing code
- Run tests after each refactor step
Never refactor while RED. Get to GREEN first.
Checklist Per Cycle
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Test would survive internal refactor
[ ] Code is minimal for this test
[ ] No speculative features added