test-driven-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest-Driven Development Skill
测试驱动开发(TDD)技能
Use this skill PROACTIVELY - Apply TDD to all code changes by default. Tests come first, implementation follows.
主动运用此技能 - 默认对所有代码变更应用TDD。先编写测试,再进行实现。
Core Philosophy
核心理念
TDD is about design, confidence, and rapid feedback. Write tests first to guide better interfaces and simpler implementations. The goal: "Clean code that works" — Ron Jeffries
TDD关乎设计、信心和快速反馈。先编写测试,以此指导设计更优的接口和更简洁的实现。目标:“可运行的整洁代码” —— 罗恩·杰夫里斯
The Red-Green-Refactor Cycle
红-绿-重构循环
This is your core workflow - use it for every code change:
这是你的核心工作流 - 所有代码变更都要遵循此流程:
Step 1: RED - Write a Failing Test
步骤1:红(RED)- 编写一个失败的测试
Write ONE test that describes the behavior you want:
typescript
describe('calculateDiscount', () => {
it('applies 10% discount when total exceeds $100', () => {
const total = calculateDiscount(150)
expect(total).toBe(135)
})
})Run the test - It MUST fail. This proves you're testing something real.
编写一个描述你所需行为的测试:
typescript
describe('calculateDiscount', () => {
it('applies 10% discount when total exceeds $100', () => {
const total = calculateDiscount(150)
expect(total).toBe(135)
})
})运行测试 - 测试必须失败。这能证明你测试的是真实的功能点。
Step 2: GREEN - Make It Pass
步骤2:绿(GREEN)- 让测试通过
Write the minimum code to make the test pass:
typescript
function calculateDiscount(amount: number): number {
if (amount > 100) {
return amount * 0.9
}
return amount
}Run the test - It should now pass. Don't add extra features or perfect the code yet.
编写最少的代码让测试通过:
typescript
function calculateDiscount(amount: number): number {
if (amount > 100) {
return amount * 0.9
}
return amount
}运行测试 - 现在测试应该通过。不要添加额外功能或优化代码。
Step 3: REFACTOR - Clean Up
步骤3:重构(REFACTOR)- 代码清理
Improve the code while keeping all tests green:
- Remove duplication
- Improve naming
- Simplify logic
- Extract functions
Run tests after each change - They must stay green.
在保持所有测试通过的前提下优化代码:
- 移除重复代码
- 优化命名
- 简化逻辑
- 提取函数
每次变更后运行测试 - 测试必须保持通过状态。
Step 4: REPEAT
步骤4:重复(REPEAT)
Pick the next test and start over. Small steps, frequent validation.
选择下一个测试,重新开始循环。小步迭代,频繁验证。
What to Test with TDD
TDD的适用场景
Apply TDD proactively to:
✅ New features - Start with a test describing desired behavior
✅ Bug fixes - Write a failing test that reproduces the bug, then fix it
✅ Refactoring - Tests ensure behavior doesn't change ✅ API endpoints - Test request/response contracts ✅ Business logic - Test calculations, validations, transformations ✅ Any code change - Default to TDD unless truly impossible
✅ Refactoring - Tests ensure behavior doesn't change ✅ API endpoints - Test request/response contracts ✅ Business logic - Test calculations, validations, transformations ✅ Any code change - Default to TDD unless truly impossible
Skip only for:
- Trivial config changes
- Pure exploratory spikes (throw away the code after)
- Generated code (test the generator or output, not both)
主动将TDD应用于:
✅ 新功能开发 - 从描述所需行为的测试开始
✅ Bug修复 - 先编写一个能复现Bug的失败测试,再修复Bug
✅ 代码重构 - 测试确保功能行为不发生变化 ✅ API接口 - 测试请求/响应契约 ✅ 业务逻辑 - 测试计算、验证、转换逻辑 ✅ 所有代码变更 - 除非确实不可行,否则默认使用TDD
✅ 代码重构 - 测试确保功能行为不发生变化 ✅ API接口 - 测试请求/响应契约 ✅ 业务逻辑 - 测试计算、验证、转换逻辑 ✅ 所有代码变更 - 除非确实不可行,否则默认使用TDD
仅在以下场景跳过TDD:
- 无关紧要的配置变更
- 纯粹的探索性实验(实验后丢弃代码)
- 生成的代码(测试生成器或输出,无需两者都测试)
Test Types to Write
需编写的测试类型
Unit Tests (Mandatory)
单元测试(必须)
Test individual functions in isolation:
typescript
describe('validateEmail', () => {
it('returns true for valid email', () => {
expect(validateEmail('user@example.com')).toBe(true)
})
it('returns false for missing @', () => {
expect(validateEmail('userexample.com')).toBe(false)
})
it('handles null gracefully', () => {
expect(validateEmail(null)).toBe(false)
})
})孤立测试单个函数:
typescript
describe('validateEmail', () => {
it('returns true for valid email', () => {
expect(validateEmail('user@example.com')).toBe(true)
})
it('returns false for missing @', () => {
expect(validateEmail('userexample.com')).toBe(false)
})
it('handles null gracefully', () => {
expect(validateEmail(null)).toBe(false)
})
})Integration Tests (For APIs & Database)
集成测试(适用于API与数据库)
Test components working together:
typescript
describe('POST /api/users', () => {
it('creates user and returns 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'test@example.com' })
expect(response.status).toBe(201)
expect(response.body.email).toBe('test@example.com')
})
it('returns 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'invalid' })
expect(response.status).toBe(400)
})
})测试组件间的协作:
typescript
describe('POST /api/users', () => {
it('creates user and returns 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'test@example.com' })
expect(response.status).toBe(201)
expect(response.body.email).toBe('test@example.com')
})
it('returns 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'invalid' })
expect(response.status).toBe(400)
})
})Edge Cases to Test
需要测试的边缘情况
Always test these scenarios:
- Null/Undefined - What if input is null?
- Empty - Empty array, empty string, zero
- Invalid Types - Wrong type passed in
- Boundaries - Min/max values, limits
- Errors - Network failures, database errors
- Special Characters - Unicode, SQL injection attempts
务必测试以下场景:
- Null/Undefined - 如果输入为null会怎样?
- 空值 - 空数组、空字符串、零值
- 无效类型 - 传入错误类型的参数
- 边界值 - 最小/最大值、限制值
- 错误场景 - 网络故障、数据库错误
- 特殊字符 - Unicode、SQL注入尝试
Mocking External Dependencies
模拟外部依赖
Mock at system boundaries to keep tests fast and reliable:
在系统边界处模拟外部依赖,以保持测试的快速与可靠:
Mock APIs
模拟API
typescript
jest.mock('@/lib/api-client', () => ({
fetchUser: jest.fn(() => Promise.resolve({
id: 1,
email: 'test@example.com'
}))
}))typescript
jest.mock('@/lib/api-client', () => ({
fetchUser: jest.fn(() => Promise.resolve({
id: 1,
email: 'test@example.com'
}))
}))Mock Database
模拟数据库
typescript
jest.mock('@/lib/database', () => ({
query: jest.fn(() => Promise.resolve([
{ id: 1, name: 'Test User' }
]))
}))Don't over-mock: Only mock external dependencies (APIs, databases, file system). Let real code run when possible.
typescript
jest.mock('@/lib/database', () => ({
query: jest.fn(() => Promise.resolve([
{ id: 1, name: 'Test User' }
]))
}))不要过度模拟:仅模拟外部依赖(API、数据库、文件系统)。尽可能让真实代码运行。
Common Mistakes to Avoid
需避免的常见错误
❌ Skipping the RED step - Always see the test fail first
❌ Writing too much code - Only write enough to pass the current test
❌ Skipping refactoring - Clean up continuously, don't accumulate debt
❌ Testing implementation - Test behavior/outputs, not internal details
❌ Over-mocking - Mock only external dependencies, not everything
❌ Writing too much code - Only write enough to pass the current test
❌ Skipping refactoring - Clean up continuously, don't accumulate debt
❌ Testing implementation - Test behavior/outputs, not internal details
❌ Over-mocking - Mock only external dependencies, not everything
❌ 跳过红阶段 - 务必先看到测试失败
❌ 编写过多代码 - 仅编写足够通过当前测试的代码
❌ 跳过重构 - 持续清理代码,不要积累技术债务
❌ 测试实现细节 - 测试行为/输出,而非内部细节
❌ 过度模拟 - 仅模拟外部依赖,而非所有内容
❌ 编写过多代码 - 仅编写足够通过当前测试的代码
❌ 跳过重构 - 持续清理代码,不要积累技术债务
❌ 测试实现细节 - 测试行为/输出,而非内部细节
❌ 过度模拟 - 仅模拟外部依赖,而非所有内容
TDD Workflow Checklist
TDD工作流检查清单
Before Starting
开始前
- Understand the behavior I'm implementing
- Test environment is set up and running
- Ready to see RED first
- 明确我要实现的功能行为
- 测试环境已搭建并运行
- 准备好先看到红阶段
Each Cycle (Repeat)
每个循环(重复)
- Write ONE test describing desired behavior
- Run test - verify it FAILS (RED)
- Write minimum code to make it pass
- Run test - verify it PASSES (GREEN)
- Refactor code while keeping tests green
- Run all tests - verify nothing broke
- 编写一个描述所需行为的测试
- 运行测试 - 确认测试失败(红阶段)
- 编写最少代码让测试通过
- 运行测试 - 确认测试通过(绿阶段)
- 在保持测试通过的前提下重构代码
- 运行所有测试 - 确认没有功能被破坏
Before Committing
提交代码前
- All tests passing
- Code is clean and readable
- No unnecessary code added
- 所有测试通过
- 代码整洁且可读性强
- 未添加不必要的代码
Quick Reference
快速参考
RED → GREEN → REFACTOR → REPEAT
RED: Write test. Watch it FAIL.
GREEN: Write minimum code to PASS.
REFACTOR: Clean up. Keep tests GREEN.Remember: Tests first, always. No production code without a failing test driving it.
红 → 绿 → 重构 → 重复
红阶段: 编写测试,观察测试失败。
绿阶段: 编写最少代码让测试通过。
重构阶段: 清理代码,保持测试通过。谨记:始终先写测试。没有失败的测试驱动,不要编写生产代码。