test-driven-development

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Test-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
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
仅在以下场景跳过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:
  1. Null/Undefined - What if input is null?
  2. Empty - Empty array, empty string, zero
  3. Invalid Types - Wrong type passed in
  4. Boundaries - Min/max values, limits
  5. Errors - Network failures, database errors
  6. Special Characters - Unicode, SQL injection attempts

务必测试以下场景:
  1. Null/Undefined - 如果输入为null会怎样?
  2. 空值 - 空数组、空字符串、零值
  3. 无效类型 - 传入错误类型的参数
  4. 边界值 - 最小/最大值、限制值
  5. 错误场景 - 网络故障、数据库错误
  6. 特殊字符 - 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

跳过红阶段 - 务必先看到测试失败
编写过多代码 - 仅编写足够通过当前测试的代码
跳过重构 - 持续清理代码,不要积累技术债务
测试实现细节 - 测试行为/输出,而非内部细节
过度模拟 - 仅模拟外部依赖,而非所有内容

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)

每个循环(重复)

  1. Write ONE test describing desired behavior
  2. Run test - verify it FAILS (RED)
  3. Write minimum code to make it pass
  4. Run test - verify it PASSES (GREEN)
  5. Refactor code while keeping tests green
  6. Run all tests - verify nothing broke
  1. 编写一个描述所需行为的测试
  2. 运行测试 - 确认测试失败(红阶段)
  3. 编写最少代码让测试通过
  4. 运行测试 - 确认测试通过(绿阶段)
  5. 在保持测试通过的前提下重构代码
  6. 运行所有测试 - 确认没有功能被破坏

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.
红 → 绿 → 重构 → 重复

红阶段:    编写测试,观察测试失败。
绿阶段:    编写最少代码让测试通过。
重构阶段:  清理代码,保持测试通过。
谨记:始终先写测试。没有失败的测试驱动,不要编写生产代码。