tdd-workflow

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Test-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 up
A test that errors on import is not a failing test. A cycle that stops at RED is not a cycle.
TDD 是一种设计工作流,而非测试技术。 编写测试是一种接口设计行为——你需要决定某个行为的调用方式。 让测试通过是一种学习行为——你会探索出最简实现方案。 重构是一种实现设计行为——你需要优化内部结构。
所有行为都诞生于这个循环:
用测试描述行为 → 实现该行为 → 代码清理
导入时出错的测试不算失败测试。停留在RED阶段的循环不算完整循环。

Workflow Overview

工作流概览

  1. Detect project context (test framework, conventions)
  2. Confirm intent with user (strict TDD vs legacy mode)
  3. Test List — enumerate behavioral scenarios (alive, evolves during coding)
  4. Cycle — for each item: Write Test → Make Pass → Refactor → Update List
  5. Verify test quality and isolation
  1. 检测项目上下文(测试框架、约定规范)
  2. 确认用户意图(严格TDD模式 vs 遗留代码模式)
  3. 测试列表——列举行为场景(动态更新,编码过程中可扩展)
  4. 循环——针对每个列表项:编写测试 → 让测试通过 → 重构 → 更新列表
  5. 验证测试质量与隔离性

Step 0: Detect Project Context

步骤0:检测项目上下文

Run
scripts/detect_test_env.sh
from the project root. If the script is unavailable, manually check:
  • Test framework (Jest, Vitest, pytest, Go test, cargo test, etc.)
  • Test file pattern (
    .test.ts
    ,
    .spec.ts
    ,
    _test.go
    ,
    test_*.py
    )
  • Test execution command (
    package.json
    scripts,
    Makefile
    , etc.)
  • 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 test

Step 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 cases
Rules:
  1. Write entries in plain language, not code
  2. Each entry describes ONE observable behavior
  3. Order from simplest/most central to complex/edge-case
  4. Share with user, then start coding — do NOT wait for exhaustive approval
  5. This list is ALIVE — add, remove, reorder items as you learn from each cycle
  6. See
    references/test-case-derivation.md
    for systematic discovery techniques
创建本次变更需支持的行为列表。这属于行为分析环节。
示例(良好的行为描述):              反面示例(实现步骤描述):
- 支持两个正数相加                - 创建Calculator类
- 0+0返回0                        - 实现add()方法
- 处理负数结果                    - 添加验证逻辑
- 拒绝非数字输入                  - 处理边缘情况
规则:
  1. 用自然语言编写条目,而非代码
  2. 每个条目描述一个可观察的行为
  3. 按从最简单/核心到复杂/边缘场景的顺序排列
  4. 与用户共享列表后即可开始编码——无需等待完整审批
  5. 列表是动态的——在每个循环中学习到新内容时,可添加、移除或重新排序条目
  6. 系统发现测试用例的技巧可参考
    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→impl3

WRITE 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
references/test-quality.md
for good/bad test patterns.
为选中的行为编写测试。编写过程中你正在设计接口:
  • 函数名称、参数、返回类型、错误格式
  • 测试是API的第一个调用者——需为调用者设计接口
采用Arrange-Act-Assert(AAA)模式。断言必须表达具体的预期值切勿使用你计划实现的相同逻辑来计算预期值。
测试的优劣模式可参考
references/test-quality.md

MAKE THE TEST RUNNABLE (This Is Not RED Yet)

让测试可运行(此时还未进入RED阶段)

Before the test can fail meaningfully, it must RUN. Create scaffolding:
python
undefined
在测试能有意义地失败之前,必须先确保它可以运行。创建脚手架代码:
python
undefined

Python: 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):
  1. Fake It (default when unsure) — return a hardcoded value:
    Test: expect(add(2, 3)).toBe(5)
    Code: return 5;   ← literally this
    The NEXT test will force generalization.
  2. Triangulation — when 2+ tests demand different hardcoded values, NOW generalize. Not before. This is how TDD drives you from specific to general.
  3. 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.
仅编写足够让当前测试通过的代码。所有之前的测试也必须保持通过状态。
三种策略(根据自信程度选择):
  1. 伪造实现(不确定时的默认策略)——返回硬编码值:
    测试:expect(add(2, 3)).toBe(5)
    代码:return 5;   ← 就写这一行
    下一个测试会强制你进行通用化改造。
  2. 三角化——当2个及以上测试需要不同的硬编码值时,再进行通用化改造。 在此之前不要提前做。这就是TDD如何引导你从具体走向通用的过程。
  3. 显而易见的实现——如果正确的通用解决方案一目了然 且极其简单,直接编写即可。如果有犹豫,就选择伪造实现。
不要添加投机性功能(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.md

Per-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

遇到困境时

ProblemSolution
Don't know how to testWrite the API you wish existed. Assert first. Ask user.
Test too complicatedDesign too coupled. Simplify the interface.
Must mock everythingCode too coupled. Use dependency injection.
Test passes immediatelyStrengthen assertions. Verify it tests NEW behavior.
Import error on first runCreate stub file/function first, then re-run.
Tempted to skip TDDSee
references/discipline.md
问题解决方案
不知道如何编写测试写出你期望存在的API。先写断言。询问用户。
测试过于复杂设计过于耦合。简化接口。
必须Mock所有对象代码过于耦合。使用依赖注入。
测试立即通过强化断言。确认它测试的是新行为。
首次运行出现导入错误先创建桩文件/函数,再重新运行。
想要跳过TDD参考
references/discipline.md

Resources

参考资源

  • references/test-quality.md
    — Good vs bad tests, naming, AAA pattern
  • references/test-case-derivation.md
    — Systematic test case discovery
  • references/mocking-guidelines.md
    — When/how to mock, test doubles
  • references/design-and-refactoring.md
    — Interface design, deep modules, refactoring
  • references/discipline.md
    — Common rationalizations, red flags
  • references/legacy-mode.md
    — Adding tests to existing code
  • scripts/detect_test_env.sh
    — Auto-detect test framework and conventions
  • references/test-quality.md
    —— 测试优劣对比、命名规范、AAA模式
  • references/test-case-derivation.md
    —— 系统的测试用例发现方法
  • references/mocking-guidelines.md
    —— Mock的时机与方式、测试替身
  • references/design-and-refactoring.md
    —— 接口设计、深度模块、重构
  • references/discipline.md
    —— 常见合理化借口、危险信号
  • references/legacy-mode.md
    —— 为现有代码添加测试
  • scripts/detect_test_env.sh
    —— 自动检测测试框架与约定规范