tdd-workflow
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest-Driven Development
测试驱动开发
The Essence
核心思想
TDD is a design workflow, not a testing technique.
Writing a test is an interface design act — you decide how a behavior should be called.
Making it pass is a learning act — you discover the simplest implementation.
Refactoring is an implementation design act — you improve internal structure.
Every behavior is born from this cycle:
Describe the behavior in a test → Make it real → Clean upA test that errors on import is not a failing test. A cycle that stops at RED is not a cycle.
TDD 是一种设计工作流,而非测试技术。
编写测试是一种接口设计行为——你需要决定某个行为的调用方式。
让测试通过是一种学习行为——你会探索出最简实现方案。
重构是一种实现设计行为——你需要优化内部结构。
所有行为都诞生于这个循环:
用测试描述行为 → 实现该行为 → 代码清理导入时出错的测试不算失败测试。停留在RED阶段的循环不算完整循环。
Workflow Overview
工作流概览
- Detect project context (test framework, conventions)
- Confirm intent with user (strict TDD vs legacy mode)
- Test List — enumerate behavioral scenarios (alive, evolves during coding)
- Cycle — for each item: Write Test → Make Pass → Refactor → Update List
- Verify test quality and isolation
- 检测项目上下文(测试框架、约定规范)
- 确认用户意图(严格TDD模式 vs 遗留代码模式)
- 测试列表——列举行为场景(动态更新,编码过程中可扩展)
- 循环——针对每个列表项:编写测试 → 让测试通过 → 重构 → 更新列表
- 验证测试质量与隔离性
Step 0: Detect Project Context
步骤0:检测项目上下文
Run from the project root. If the script is unavailable, manually check:
scripts/detect_test_env.sh- Test framework (Jest, Vitest, pytest, Go test, cargo test, etc.)
- Test file pattern (,
.test.ts,.spec.ts,_test.go)test_*.py - Test execution command (scripts,
package.json, etc.)Makefile - Existing test directory structure
Adapt all subsequent commands to the detected framework. Never assume .
npm test从项目根目录运行脚本。若脚本不可用,则手动检查:
scripts/detect_test_env.sh- 测试框架(Jest、Vitest、pytest、Go test、cargo test等)
- 测试文件命名模式(、
.test.ts、.spec.ts、_test.go)test_*.py - 测试执行命令(脚本、
package.json等)Makefile - 现有测试目录结构
后续所有命令都需适配检测到的框架。切勿默认使用。
npm testStep 1: Confirm User Intent
步骤1:确认用户意图
Strict TDD (default for new features/bug fixes):
- Write failing test first, then implement
Legacy mode (existing code without tests):
- See
references/legacy-mode.md
Not applicable — skip TDD for:
- Configuration files, auto-generated code, declarative CSS, throwaway prototypes
严格TDD模式(新功能/ bug修复默认模式):
- 先编写失败的测试,再实现功能
遗留代码模式(针对无测试的现有代码):
- 参考
references/legacy-mode.md
不适用场景——以下情况跳过TDD:
- 配置文件、自动生成的代码、声明式CSS、一次性原型
Step 2: Test List (Dynamic)
步骤2:测试列表(动态)
Create a list of behaviors this change needs to support. This is behavioral analysis.
GOOD (behaviors): BAD (implementation steps):
- adds two positive numbers - create Calculator class
- returns 0 for 0 + 0 - implement add() method
- handles negative results - add validation logic
- rejects non-numeric input - handle edge casesRules:
- Write entries in plain language, not code
- Each entry describes ONE observable behavior
- Order from simplest/most central to complex/edge-case
- Share with user, then start coding — do NOT wait for exhaustive approval
- This list is ALIVE — add, remove, reorder items as you learn from each cycle
- See for systematic discovery techniques
references/test-case-derivation.md
创建本次变更需支持的行为列表。这属于行为分析环节。
示例(良好的行为描述): 反面示例(实现步骤描述):
- 支持两个正数相加 - 创建Calculator类
- 0+0返回0 - 实现add()方法
- 处理负数结果 - 添加验证逻辑
- 拒绝非数字输入 - 处理边缘情况规则:
- 用自然语言编写条目,而非代码
- 每个条目描述一个可观察的行为
- 按从最简单/核心到复杂/边缘场景的顺序排列
- 与用户共享列表后即可开始编码——无需等待完整审批
- 列表是动态的——在每个循环中学习到新内容时,可添加、移除或重新排序条目
- 系统发现测试用例的技巧可参考
references/test-case-derivation.md
Step 3: TDD Cycles
步骤3:TDD循环
One cycle = one behavior. A cycle is NOT complete until GREEN.
一个循环对应一个行为。只有进入GREEN阶段,循环才算完成。
Pick one item from the test list. Execute this cycle:
从测试列表中选取一个条目,执行以下循环:
DO NOT write all tests first, then all implementation.
切勿先编写所有测试,再统一实现功能。
WRONG (horizontal): test1, test2, test3 → impl1, impl2, impl3
RIGHT (vertical): test1→impl1 → test2→impl2 → test3→impl3错误方式(横向推进): test1, test2, test3 → impl1, impl2, impl3
正确方式(垂直切片): test1→impl1 → test2→impl2 → test3→impl3WRITE THE TEST (Interface Design Happens Here)
编写测试(接口设计在此完成)
Write a test for the chosen behavior. As you write, you are designing the interface:
- Function name, parameters, return type, error format
- The test IS the first client of the API — design for the caller
Use Arrange-Act-Assert. Your assertion must express a CONCRETE expected value.
Never compute the expected value with the same logic you plan to implement.
See for good/bad test patterns.
references/test-quality.md为选中的行为编写测试。编写过程中你正在设计接口:
- 函数名称、参数、返回类型、错误格式
- 测试是API的第一个调用者——需为调用者设计接口
采用Arrange-Act-Assert(AAA)模式。断言必须表达具体的预期值。
切勿使用你计划实现的相同逻辑来计算预期值。
测试的优劣模式可参考。
references/test-quality.mdMAKE THE TEST RUNNABLE (This Is Not RED Yet)
让测试可运行(此时还未进入RED阶段)
Before the test can fail meaningfully, it must RUN. Create scaffolding:
python
undefined在测试能有意义地失败之前,必须先确保它可以运行。创建脚手架代码:
python
undefinedPython: create calculator.py
Python: 创建calculator.py
def add(a, b):
pass
```typescript
// TypeScript: create calculator.ts
export function add(a: number, b: number): number {
return undefined as any;
}go
// Go: create calculator.go
func Add(a, b int) int {
return 0
}These stubs are NOT production code. They are scaffolding so the test runner
can execute your test and reach the assertion.
def add(a, b):
pass
```typescript
// TypeScript: 创建calculator.ts
export function add(a: number, b: number): number {
return undefined as any;
}go
// Go: 创建calculator.go
func Add(a, b int) int {
return 0
}这些桩代码不是生产代码。它们只是脚手架,用于让测试运行器能够执行测试并到达断言环节。
RED — Confirm the Test Fails for the Right Reason
RED阶段——确认测试因正确的原因失败
Run the test. Classify the result:
VALID RED — assertion fails with wrong value:
✗ Expected 5 but received 0
✗ Expected "confirmed" but received undefined
✗ Expected function to throw but it did not→ Proceed to GREEN.
INVALID — infrastructure error (test never reached the assertion):
✗ Cannot find module './calculator'
✗ TypeError: add is not a function
✗ SyntaxError: Unexpected token→ Fix scaffolding (create file, add stub). Re-run. Loop until you get a VALID RED.
INVALID — test passes immediately:
→ Test is wrong. It tests existing behavior or has weak assertions. Rewrite.
The rule: your assertion line must EXECUTE and FAIL.
运行测试,对结果分类:
有效RED——断言因结果错误而失败:
✗ 预期为5,但实际得到0
✗ 预期为"confirmed",但实际得到undefined
✗ 预期函数抛出异常,但未抛出→ 进入GREEN阶段。
无效——基础设施错误(测试从未执行到断言):
✗ 找不到模块'./calculator'
✗ 类型错误:add不是函数
✗ 语法错误:意外的标记→ 修复脚手架(创建文件、添加桩代码)。重新运行。循环直至得到有效RED结果。
无效——测试立即通过:
→ 测试存在问题。它测试的是现有行为,或者断言过于宽松。重写测试。
规则:断言代码必须执行并失败。
GREEN — Make It Pass with Minimal Code
GREEN阶段——用最少的代码让测试通过
Write just enough code to make THIS test pass. All previous tests must also pass.
Three strategies (choose based on confidence):
-
Fake It (default when unsure) — return a hardcoded value:
Test: expect(add(2, 3)).toBe(5) Code: return 5; ← literally thisThe NEXT test will force generalization. -
Triangulation — when 2+ tests demand different hardcoded values, NOW generalize. Not before. This is how TDD drives you from specific to general.
-
Obvious Implementation — if the correct general solution is immediately clear AND trivially simple, write it. If you hesitate, Fake It instead.
No speculative features (YAGNI). No refactoring yet.
仅编写足够让当前测试通过的代码。所有之前的测试也必须保持通过状态。
三种策略(根据自信程度选择):
-
伪造实现(不确定时的默认策略)——返回硬编码值:
测试:expect(add(2, 3)).toBe(5) 代码:return 5; ← 就写这一行下一个测试会强制你进行通用化改造。 -
三角化——当2个及以上测试需要不同的硬编码值时,再进行通用化改造。 在此之前不要提前做。这就是TDD如何引导你从具体走向通用的过程。
-
显而易见的实现——如果正确的通用解决方案一目了然 且极其简单,直接编写即可。如果有犹豫,就选择伪造实现。
不要添加投机性功能(YAGNI原则)。此时不要进行重构。
REFACTOR (Only When Green)
REFACTOR阶段——仅当所有测试都通过时进行
All tests pass. Now improve the code:
- Remove duplication (but duplication is a hint, not a command)
- Improve names, extract helpers, simplify structure
- Run tests after EVERY change — stay GREEN
- Never add behavior during refactor (new return value or exception = new behavior = new test first)
- See
references/design-and-refactoring.md
所有测试都通过后,优化代码:
- 移除重复代码(但重复只是提示,而非强制命令)
- 优化命名、提取辅助函数、简化结构
- 每做一次变更就运行测试——保持GREEN状态
- 重构时切勿添加新行为(新的返回值或异常属于新行为,需先编写新测试)
- 参考
references/design-and-refactoring.md
UPDATE TEST LIST AND REPEAT
更新测试列表并重复
After each cycle:
- Did you discover a new case? Add it to the list.
- Is an item no longer relevant? Remove it.
- Pick the next item and repeat until the list is empty.
每个循环结束后:
- 是否发现了新的测试用例?添加到列表中。
- 是否有条目不再相关?移除它。
- 选择下一个条目,重复循环直至列表为空。
Mocking Rules
Mocking规则
Mock ONLY at system boundaries: external APIs, databases (prefer test DB), time, randomness.
Never mock your own classes or internal collaborators.
See .
references/mocking-guidelines.md仅在系统边界处使用Mock:外部API、数据库(优先使用测试数据库)、时间、随机数。
切勿Mock自己的类或内部协作对象。
参考。
references/mocking-guidelines.mdPer-Cycle Checklist (all must be true before reporting to user)
单循环检查清单(向用户汇报前需全部满足)
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Assertion executed and failed with WRONG VALUE (not import/type error)
[ ] Wrote minimal code to make test pass (Fake It / Triangulation / Obvious)
[ ] ALL tests pass (including pre-existing)
[ ] No speculative features added
[ ] Reported result AFTER GREEN, not after RED[ ] 测试描述的是行为,而非实现细节
[ ] 测试仅使用公共接口
[ ] 断言代码已执行且因结果错误失败(而非导入/类型错误)
[ ] 编写了最少的代码让测试通过(伪造实现/三角化/显而易见的实现)
[ ] 所有测试都通过(包括已有的测试)
[ ] 未添加投机性功能
[ ] 在GREEN阶段后再向用户汇报结果,而非RED阶段Completion Checklist
完成检查清单
[ ] Every behavior has a test that was seen failing (assertion failure) first
[ ] Edge cases and error paths covered
[ ] All tests pass with clean output
[ ] Tests run independently (no order dependency)
[ ] Test names read as behavior specifications[ ] 每个行为都有先失败(断言失败)的测试覆盖
[ ] 覆盖了边缘情况和错误路径
[ ] 所有测试都通过,输出清晰
[ ] 测试可独立运行(无顺序依赖)
[ ] 测试名称可作为行为规范阅读When Stuck
遇到困境时
| Problem | Solution |
|---|---|
| Don't know how to test | Write the API you wish existed. Assert first. Ask user. |
| Test too complicated | Design too coupled. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test passes immediately | Strengthen assertions. Verify it tests NEW behavior. |
| Import error on first run | Create stub file/function first, then re-run. |
| Tempted to skip TDD | See |
| 问题 | 解决方案 |
|---|---|
| 不知道如何编写测试 | 写出你期望存在的API。先写断言。询问用户。 |
| 测试过于复杂 | 设计过于耦合。简化接口。 |
| 必须Mock所有对象 | 代码过于耦合。使用依赖注入。 |
| 测试立即通过 | 强化断言。确认它测试的是新行为。 |
| 首次运行出现导入错误 | 先创建桩文件/函数,再重新运行。 |
| 想要跳过TDD | 参考 |
Resources
参考资源
- — Good vs bad tests, naming, AAA pattern
references/test-quality.md - — Systematic test case discovery
references/test-case-derivation.md - — When/how to mock, test doubles
references/mocking-guidelines.md - — Interface design, deep modules, refactoring
references/design-and-refactoring.md - — Common rationalizations, red flags
references/discipline.md - — Adding tests to existing code
references/legacy-mode.md - — Auto-detect test framework and conventions
scripts/detect_test_env.sh
- —— 测试优劣对比、命名规范、AAA模式
references/test-quality.md - —— 系统的测试用例发现方法
references/test-case-derivation.md - —— Mock的时机与方式、测试替身
references/mocking-guidelines.md - —— 接口设计、深度模块、重构
references/design-and-refactoring.md - —— 常见合理化借口、危险信号
references/discipline.md - —— 为现有代码添加测试
references/legacy-mode.md - —— 自动检测测试框架与约定规范
scripts/detect_test_env.sh