tdd-red-green-refactor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest-Driven Development: Red-Green-Refactor
测试驱动开发:Red-Green-Refactor
Core Principle
核心原则
Tests verify behavior through public interfaces, not implementation details.
A test that breaks when you refactor internals -- but behavior is unchanged --
is testing implementation, not behavior. Good tests survive refactors.
When fixing a bug: prove it exists with a failing test before touching
production code. The test is evidence. The fix is the response to that evidence.
测试通过公共接口验证行为,而非实现细节。如果在重构内部代码但行为未改变时测试失败,说明该测试针对的是实现而非行为。优质的测试能够在重构后依然通过。
修复缺陷时:在修改生产代码前,先编写一个失败的测试用例来证明缺陷存在。测试是证据,修复则是对该证据的回应。
Workflow Overview
工作流概述
RED -> Write a test that fails (proves the bug or defines missing behavior)
GREEN -> Write the minimum code to make the test pass
REFACTOR -> Improve structure, naming, duplication -- tests stay greenOne cycle per behavior. Vertical slices, not horizontal.
RED -> Write a test that fails (proves the bug or defines missing behavior)
GREEN -> Write the minimum code to make the test pass
REFACTOR -> Improve structure, naming, duplication -- tests stay green每个行为对应一个循环。采用垂直切片,而非水平切片。
Phase 1: RED -- Establish Failure
阶段1:RED —— 确认失败
For Bug Fixes
缺陷修复场景
- Reproduce the bug -- identify the exact input, state, or sequence that triggers the defect.
- Write a test that exercises the buggy code path with the offending input.
- Assert the correct (expected) behavior, not the current broken output.
- Run the test -- it must fail. If it passes, your test is not capturing the bug. Rethink your assertion or test setup.
- Name the test descriptively -- include the bug/ticket reference if one
exists (e.g., ).
test_bug_1234_negative_balance_rejected
- 复现缺陷——确定触发缺陷的具体输入、状态或操作序列。
- 编写测试用例——针对有问题的代码路径,传入导致故障的输入。
- 断言正确(预期)的行为,而非当前的错误输出。
- 运行测试——测试必须失败。如果测试通过,说明你的测试未捕获到缺陷。重新考虑断言或测试设置。
- 为测试用例起描述性名称——如果有缺陷/工单编号,可包含在内(例如:)。
test_bug_1234_negative_balance_rejected
For New Features
新功能开发场景
- Define one behavior the feature should exhibit.
- Write a test for that single behavior using the public API/interface.
- Run the test -- confirm it fails (the feature does not exist yet).
- 定义一个功能行为——明确新功能应具备的一项行为。
- 编写测试用例——针对该单一行为,使用公共API/接口编写测试。
- 运行测试——确认测试失败(因为功能尚未实现)。
RED Phase Rules
RED阶段规则
- The test must fail for the right reason (missing behavior, not a compile error or import failure).
- If you cannot write a test, that is a design signal: the code is not testable enough. Address testability first.
- Do not write multiple tests at once. One test, one behavior.
- 测试必须因正确的原因失败(缺少行为,而非编译错误或导入失败)。
- 如果无法编写测试用例,这是一个设计信号:代码的可测试性不足。应先解决可测试性问题。
- 不要同时编写多个测试用例。一个测试对应一个行为。
Hypothesis-Driven Bug Investigation
基于假设的缺陷排查
When the bug's root cause is unclear:
- Brainstorm multiple hypotheses about what causes the defect.
- Prioritize by likelihood and cost to falsify.
- Write a test targeting the top hypothesis.
- Timebox investigation -- if a hypothesis does not pan out within the timebox, move to the next one.
- A test that passes unexpectedly is useful data: it eliminates a hypothesis.
当缺陷的根本原因不明确时:
- 构思多个关于缺陷原因的假设。
- 根据可能性和验证成本排序。
- 针对优先级最高的假设编写测试用例。
- 设定排查时间盒——如果在时间盒内假设不成立,则转向下一个假设。
- 意外通过的测试也是有用的数据:它排除了一个假设。
Phase 2: GREEN -- Minimal Implementation
阶段2:GREEN —— 最小化实现
- Write the smallest, simplest code that makes the failing test pass.
- Do not add features, abstractions, or optimizations not required by the test.
- Do not anticipate future tests -- solve only the current one.
- Run all tests -- the new test passes and no existing tests broke.
- 编写最小、最简单的代码使失败的测试通过。
- 不要添加测试未要求的功能、抽象或优化。
- 不要预判未来的测试——只解决当前测试的问题。
- 运行所有测试——新测试通过,且现有测试未被破坏。
GREEN Phase Rules
GREEN阶段规则
- Minimal is enough: ugly code is fine at this stage. Correctness over elegance.
- If an existing test breaks, your change introduced a regression. Fix it before proceeding.
- If you find yourself writing significant code, consider whether you skipped a smaller intermediate test.
- 最小化即可:此阶段代码丑陋也无妨。正确性优先于优雅性。
- 如果现有测试失败,说明你的修改引入了回归。在继续之前修复该问题。
- 如果你发现自己在编写大量代码,考虑是否跳过了更小的中间测试用例。
Phase 3: REFACTOR -- Improve Structure
阶段3:REFACTOR —— 优化结构
Only enter this phase when all tests are green.
- Look for duplication, unclear naming, or structural issues.
- Apply one refactoring at a time.
- Run tests after each change -- they must remain green.
- Common refactorings at this stage:
- Extract shared logic into functions/methods
- Rename for clarity
- Simplify conditionals
- Move code to more appropriate modules
- Deepen modules (smaller public interface, richer implementation)
只有当**所有测试都通过(呈GREEN状态)**时,才能进入此阶段。
- 查找重复代码、命名不清晰或结构问题。
- 一次应用一项重构操作。
- 每次修改后运行测试——测试必须保持通过状态。
- 此阶段常见的重构操作:
- 将共享逻辑提取到函数/方法中
- 重命名以提升清晰度
- 简化条件判断
- 将代码移动到更合适的模块
- 深化模块(更小的公共接口,更丰富的实现)
REFACTOR Phase Rules
REFACTOR阶段规则
- Never refactor while RED. Get to GREEN first.
- If a refactoring breaks a test, undo and take a smaller step.
- Do not add new behavior during refactoring. That is a new RED phase.
- Refactoring is optional per cycle -- skip if the code is clean enough.
- 绝不要在RED状态下进行重构。先达到GREEN状态。
- 如果重构破坏了测试,撤销修改并采取更小的步骤。
- 重构期间不要添加新行为。那属于新的RED阶段。
- 每个循环的重构是可选的——如果代码足够简洁,可以跳过。
Bug Fix Workflow (Detailed)
缺陷修复工作流(详细版)
This is the primary use case. When encountering a bug:
1. UNDERSTAND -> Reproduce and isolate the defect
2. RED -> Write a test asserting correct behavior (test fails)
3. GREEN -> Fix the bug with minimal code (test passes)
4. REFACTOR -> Clean up if needed (tests stay green)
5. VERIFY -> Run full test suite; confirm no regressions这是主要用例。遇到缺陷时:
1. UNDERSTAND -> Reproduce and isolate the defect
2. RED -> Write a test asserting correct behavior (test fails)
3. GREEN -> Fix the bug with minimal code (test passes)
4. REFACTOR -> Clean up if needed (tests stay green)
5. VERIFY -> Run full test suite; confirm no regressionsSeparation of Concerns in PRs
Pull Request中的关注点分离
For team workflows, consider splitting into two commits or PRs:
Commit/PR 1 -- Expose the bug:
- Add the failing test that demonstrates the defect
- Assert the correct expected behavior (test will fail)
- This proves the bug is real and reproducible
Commit/PR 2 -- Fix the bug:
- Change production code to fix the defect
- The previously failing test now passes
- This proves the fix addresses the exact bug
This separation provides auditable evidence that the test actually catches the
defect, not that it was written after-the-fact to rubberstamp a fix.
对于团队工作流,考虑拆分为两个提交或PR:
提交/PR 1 —— 暴露缺陷:
- 添加展示缺陷的失败测试用例
- 断言正确的预期行为(测试会失败)
- 这证明缺陷真实存在且可复现
提交/PR 2 —— 修复缺陷:
- 修改生产代码以修复缺陷
- 之前失败的测试现在通过
- 这证明修复恰好解决了该缺陷
这种分离提供了可审计的证据,证明测试确实能捕获缺陷,而非事后编写来盖章确认修复。
Anti-Patterns
反模式
Horizontal Slicing (write all tests, then all code)
水平切片(先编写所有测试,再编写所有代码)
Tests written in bulk test imagined behavior. You end up testing shapes and
signatures instead of actual behavior. Tests become insensitive to real changes.
WRONG:
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT:
RED->GREEN: test1 -> impl1
RED->GREEN: test2 -> impl2
RED->GREEN: test3 -> impl3批量编写的测试针对的是想象中的行为。最终你会测试形状和签名,而非实际行为。测试对真实变化不敏感。
WRONG:
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT:
RED->GREEN: test1 -> impl1
RED->GREEN: test2 -> impl2
RED->GREEN: test3 -> impl3Testing Implementation Instead of Behavior
测试实现而非行为
Bad signals:
- Test mocks internal collaborators
- Test accesses private methods or fields
- Test verifies internal state (e.g., querying a database directly instead of using the public interface)
- Test breaks when you rename an internal function
不良信号:
- 测试模拟内部协作对象
- 测试访问私有方法或字段
- 测试验证内部状态(例如,直接查询数据库而非使用公共接口)
- 当你重命名内部函数时,测试失败
Skipping RED
跳过RED阶段
Writing tests after the implementation ("test-after") does not provide the
design feedback that TDD gives. If the test never failed, you have no proof it
can catch regressions.
在实现后编写测试(“事后测试”)无法提供TDD带来的设计反馈。如果测试从未失败,你无法证明它能捕获回归。
Gold-Plating in GREEN
GREEN阶段过度设计
Adding abstractions, optimizations, or extra features during the GREEN phase.
The GREEN phase is about correctness, not elegance. Save structural improvements
for REFACTOR.
在GREEN阶段添加抽象、优化或额外功能。GREEN阶段的重点是正确性,而非优雅性。结构优化留到REFACTOR阶段。
Refactoring While RED
在RED状态下重构
Changing structure while tests are failing makes it impossible to distinguish
between test failures from the original defect and new failures from your
refactoring.
在测试失败时修改结构,无法区分测试失败是源于原始缺陷还是你的重构引入的新问题。
Per-Cycle Checklist
每个循环的检查清单
Use this mental checklist for each RED-GREEN-REFACTOR cycle:
[ ] Test describes behavior, not implementation
[ ] Test uses the public interface only
[ ] Test would survive an internal refactor
[ ] Test fails for the right reason (RED)
[ ] Implementation is minimal for this test (GREEN)
[ ] No speculative features added (GREEN)
[ ] All tests pass after refactoring (REFACTOR)
[ ] No new behavior introduced during refactor在每个RED-GREEN-REFACTOR循环中使用此心理检查清单:
[ ] Test describes behavior, not implementation
[ ] Test uses the public interface only
[ ] Test would survive an internal refactor
[ ] Test fails for the right reason (RED)
[ ] Implementation is minimal for this test (GREEN)
[ ] No speculative features added (GREEN)
[ ] All tests pass after refactoring (REFACTOR)
[ ] No new behavior introduced during refactorLanguage-Specific Guidance
语言特定指南
Rust
Rust
- Use and
#[test]for unit tests#[should_panic] - Place integration tests in directory
tests/ - Use to run;
cargo testfor stdoutcargo test -- --nocapture - Consider for test modules alongside source
#[cfg(test)] mod tests - Use ,
assert_eq!,assert_ne!macrosassert! - For async tests: with tokio runtime
#[tokio::test]
- 单元测试使用和
#[test]属性#[should_panic] - 集成测试放在目录下
tests/ - 使用运行;
cargo test查看标准输出cargo test -- --nocapture - 考虑在源代码旁使用定义测试模块
#[cfg(test)] mod tests - 使用、
assert_eq!、assert_ne!宏assert! - 异步测试:搭配tokio运行时使用
#[tokio::test]
Go
Go
- Use file suffix and
_test.gosignaturefunc TestXxx(t *testing.T) - Run with
go test ./... - Use /
t.Errorffor assertionst.Fatalf - Table-driven tests are idiomatic for testing multiple inputs
- Use for subtests
t.Run
- 使用文件后缀和
_test.go签名func TestXxx(t *testing.T) - 使用运行测试
go test ./... - 使用/
t.Errorf进行断言t.Fatalf - 表驱动测试是测试多输入的惯用方式
- 使用进行子测试
t.Run
TypeScript
TypeScript
- Use test frameworks like vitest, jest, or node:test
- Run with the appropriate test runner command
- Use /
describe/itpatternexpect - For async: return promises or use /
asyncin test functionsawait
- 使用vitest、jest或node:test等测试框架
- 使用相应的测试运行器命令执行
- 使用/
describe/it模式expect - 异步测试:返回Promise或在测试函数中使用/
asyncawait
Solidity
Solidity
- Use Foundry's with
forge testnamingfunction test_* - Use ,
assertEq,assertTruefor assertionsvm.expectRevert - Fork tests with for mainnet state
vm.createFork - Use for test fixtures
setUp() - Fuzz tests: for property-based testing
function testFuzz_*(uint256 x)
- 使用Foundry的,测试函数命名遵循
forge test格式function test_* - 使用、
assertEq、assertTrue进行断言vm.expectRevert - 使用进行主网状态的分叉测试
vm.createFork - 使用设置测试夹具
setUp() - 模糊测试:使用进行基于属性的测试
function testFuzz_*(uint256 x)
When the Bug is Hard to Test
当缺陷难以测试时
If writing a test is difficult or the environment lacks test infrastructure:
- Write a test that fails with an explicit message explaining the bug and why testing is hard.
- Fix the bug.
- Replace the explicit failure with a proper assertion once testability improves.
- Invest in making the code more testable -- this is a design improvement.
如果编写测试用例困难,或者环境缺乏测试基础设施:
- 编写一个失败的测试用例,附带明确的消息说明缺陷以及测试困难的原因。
- 修复缺陷。
- 一旦可测试性提升,将明确的失败消息替换为正确的断言。
- 投入资源提升代码的可测试性——这是一项设计改进。
Additional Resources
额外资源
- For concrete examples per language, see references/examples.md
- 各语言的具体示例,请参阅references/examples.md