writing-tests

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Writing Tests

编写测试

Core Philosophy: Test user-observable behavior with real dependencies. Tests should survive refactoring when behavior is unchanged.
Iron Laws:
<IMPORTANT> 1. Test real behavior, not mock behavior 2. Never add test-only methods to production code 3. Never mock without understanding dependencies </IMPORTANT>
核心理念: 测试用户可观测的行为,使用真实依赖项。当行为未发生变化时,测试应能在重构后依然通过。
铁则:
<IMPORTANT> 1. Test real behavior, not mock behavior 2. Never add test-only methods to production code 3. Never mock without understanding dependencies </IMPORTANT>

Testing Trophy Model

Testing Trophy模型

Write tests in this priority order:
  1. Integration Tests (PRIMARY) - Multiple units with real dependencies
  2. E2E Tests (SECONDARY) - Complete workflows across the stack
  3. Unit Tests (RARE) - Pure functions only (no dependencies)
Default to integration tests. Only drop to unit tests for pure utility functions.
按照以下优先级编写测试:
  1. 集成测试(首要选择) - 多个单元,使用真实依赖项
  2. E2E测试(次要选择) - 覆盖全栈的完整工作流
  3. 单元测试(极少使用) - 仅用于纯函数(无依赖项)
默认使用集成测试。仅针对纯工具函数编写单元测试。

Pre-Test Workflow

测试前工作流

BEFORE writing any tests, copy this checklist and track your progress:
Test Writing Progress:
- [ ] Step 1: Review project standards (check existing tests)
- [ ] Step 2: Understand behavior (what should it do? what can fail?)
- [ ] Step 3: Choose test type (Integration/E2E/Unit)
- [ ] Step 4: Identify dependencies (real vs mocked)
- [ ] Step 5: Write failing test first (TDD)
- [ ] Step 6: Implement minimal code to pass
- [ ] Step 7: Verify coverage (happy path, errors, edge cases)
Before writing any tests:
  1. Review project standards - Check existing test files, testing docs, or project conventions
  2. Understand behavior - What should this do? What can go wrong?
  3. Choose test type - Integration (default), E2E (critical workflows), or Unit (pure functions)
  4. Identify dependencies - What needs to be real vs mocked?
在编写任何测试之前,复制以下检查清单并跟踪进度:
Test Writing Progress:
- [ ] Step 1: Review project standards (check existing tests)
- [ ] Step 2: Understand behavior (what should it do? what can fail?)
- [ ] Step 3: Choose test type (Integration/E2E/Unit)
- [ ] Step 4: Identify dependencies (real vs mocked)
- [ ] Step 5: Write failing test first (TDD)
- [ ] Step 6: Implement minimal code to pass
- [ ] Step 7: Verify coverage (happy path, errors, edge cases)
编写测试前需完成:
  1. 评审项目标准 - 查看现有测试文件、测试文档或项目约定
  2. 理解行为逻辑 - 该功能应该实现什么?可能出现哪些故障?
  3. 选择测试类型 - 集成测试(默认)、E2E测试(关键工作流)或单元测试(纯函数)
  4. 识别依赖项 - 哪些需要使用真实依赖,哪些需要模拟?

Test Type Decision

测试类型决策

Is this a complete user workflow?
  → YES: E2E test

Is this a pure function (no side effects/dependencies)?
  → YES: Unit test

Everything else:
  → Integration test (with real dependencies)
Is this a complete user workflow?
  → YES: E2E test

Is this a pure function (no side effects/dependencies)?
  → YES: Unit test

Everything else:
  → Integration test (with real dependencies)

Mocking Guidelines

模拟对象准则

Default: Don't mock. Use real dependencies.
默认规则:不使用模拟,使用真实依赖项。

Only Mock These

仅在以下场景使用模拟

  • External HTTP/API calls
  • Time-dependent operations (timers, dates)
  • Randomness (random numbers, UUIDs)
  • File system I/O
  • Third-party services (payments, analytics, email)
  • Network boundaries
  • 外部HTTP/API调用
  • 时间相关操作(定时器、日期)
  • 随机操作(随机数、UUID)
  • 文件系统I/O
  • 第三方服务(支付、分析、邮件)
  • 网络边界

Never Mock These

绝不在以下场景使用模拟

  • Internal modules/packages
  • Database queries (use test database)
  • Business logic
  • Data transformations
  • Your own code calling your own code
Why: Mocking internal dependencies creates brittle tests that break during refactoring.
  • 内部模块/包
  • 数据库查询(使用测试数据库)
  • 业务逻辑
  • 数据转换
  • 自有代码调用自有代码
原因: 模拟内部依赖会导致测试变得脆弱,在重构时容易失败。

Before Mocking, Ask:

使用模拟前,请先自问:

  1. "What side effects does this method have?"
  2. "Does my test depend on those side effects?"
  3. If yes → Mock at lower level (the slow/external operation, not the method test needs)
  4. Unsure? → Run with real implementation first, observe what's needed, THEN add minimal mocking
  1. "该方法会产生哪些副作用?"
  2. "我的测试是否依赖这些副作用?"
  3. 如果是 → 在更低层级进行模拟(针对缓慢/外部操作,而非测试所需的方法)
  4. 不确定? → 先使用真实实现运行,观察需求,再添加最少必要的模拟

Mock Red Flags

模拟对象的危险信号

  • "I'll mock this to be safe"
  • "This might be slow, better mock it"
  • Can't explain why mock is needed
  • Mock setup longer than test logic
  • Test fails when removing mock
  • "我模拟这个只是为了安全起见"
  • "这个可能很慢,最好模拟它"
  • 无法解释使用模拟的原因
  • 模拟设置的代码比测试逻辑更长
  • 移除模拟后测试失败

Integration Test Pattern

集成测试模式

describe("Feature Name", () => {
  setup(initialState)

  test("should produce expected output when action is performed", () => {
    // Arrange: Set up preconditions
    // Act: Perform the action being tested
    // Assert: Verify observable output
  })
})
Key principles:
  • Use real state/data, not mocks
  • Assert on outputs users/callers can observe
  • Test the behavior, not the implementation
For language-specific patterns, see the Language-Specific Patterns section.
describe("Feature Name", () => {
  setup(initialState)

  test("should produce expected output when action is performed", () => {
    // Arrange: Set up preconditions
    // Act: Perform the action being tested
    // Assert: Verify observable output
  })
})
核心原则:
  • 使用真实状态/数据,而非模拟对象
  • 断言用户/调用方可观测的输出
  • 测试行为,而非实现细节
如需特定语言的模式,请查看「特定语言模式」章节。

Async Waiting Patterns

异步等待模式

When tests involve async operations, avoid arbitrary timeouts:
// BAD: Guessing at timing
sleep(500)
assert result == expected

// GOOD: Wait for the actual condition
wait_for(lambda: result == expected)
When to use condition-based waiting:
  • Tests use
    sleep
    ,
    setTimeout
    , or arbitrary delays
  • Tests are flaky (pass locally, fail in CI)
  • Tests timeout when run in parallel
  • Waiting for async operations to complete
Delegate to skill: When you encounter these patterns, invoke
Skill(ce:condition-based-waiting)
for detailed guidance on implementing proper condition polling and fixing flaky tests.
当测试涉及异步操作时,避免使用任意时长的超时:
// BAD: Guessing at timing
sleep(500)
assert result == expected

// GOOD: Wait for the actual condition
wait_for(lambda: result == expected)
何时使用基于条件的等待:
  • 测试中使用
    sleep
    setTimeout
    或任意延迟
  • 测试不稳定(本地通过,CI环境失败)
  • 并行运行时测试超时
  • 等待异步操作完成
技能调用提示: 当遇到这些模式时,调用
Skill(ce:condition-based-waiting)
获取关于实现正确条件轮询和修复不稳定测试的详细指导。

Assertion Strategy

断言策略

Principle: Assert on observable outputs, not internal state.
ContextAssert OnAvoid
UIVisible text, accessibility roles, user-visible stateCSS classes, internal state, test IDs
APIResponse body, status code, headersInternal DB state directly
CLIstdout/stderr, exit codeInternal variables
LibraryReturn values, documented side effectsPrivate methods, internal state
Why: Tests that assert on implementation details break when you refactor, even if behavior is unchanged.
原则:断言可观测的输出,而非内部状态。
场景应断言的内容应避免的内容
UI可见文本、无障碍角色、用户可见状态CSS类、内部状态、测试ID
API响应体、状态码、请求头直接断言内部数据库状态
CLIstdout/stderr、退出码内部变量
返回值、文档化的副作用私有方法、内部状态
原因: 断言实现细节的测试会在重构时失败,即使行为未发生变化。

Test Data Management

测试数据管理

Use source constants and fixtures, not hard-coded values:
// Good - References actual constant or fixture
expected_message = APP_MESSAGES.SUCCESS
assert response.message == expected_message

// Bad - Hard-coded, breaks when copy changes
assert response.message == "Action completed successfully!"
Why: When product copy changes, you want one place to update, not every test file.
使用源常量和测试数据,而非硬编码值:
// Good - References actual constant or fixture
expected_message = APP_MESSAGES.SUCCESS
assert response.message == expected_message

// Bad - Hard-coded, breaks when copy changes
assert response.message == "Action completed successfully!"
原因: 当产品文案变更时,你只需要在一处更新,而非修改所有测试文件。

Anti-Patterns to Avoid

需避免的反模式

Testing Mock Behavior

测试模拟对象的行为

// BAD: Testing that the mock was called, not real behavior
mock_service.assert_called_once()

// GOOD: Test the actual outcome
assert user.is_active == True
assert len(sent_emails) == 1
Gate: Before asserting on mock calls, ask "Am I testing real behavior or mock interactions?" If testing mocks → Stop, test the actual outcome instead.
// BAD: Testing that the mock was called, not real behavior
mock_service.assert_called_once()

// GOOD: Test the actual outcome
assert user.is_active == True
assert len(sent_emails) == 1
检查点: 在断言模拟对象的调用情况之前,自问「我是在测试真实行为还是模拟对象的交互?」如果是测试模拟对象 → 停止,改为测试实际结果。

Test-Only Methods in Production

生产代码中的仅测试方法

// BAD: destroy() only used in tests - pollutes production code
class Session:
    def destroy(self):  # Only exists for test cleanup
        ...

// GOOD: Test utilities handle cleanup
// BAD: destroy() only used in tests - pollutes production code
class Session:
    def destroy(self):  # Only exists for test cleanup
        ...

// GOOD: Test utilities handle cleanup

In test_utils.py

In test_utils.py

def cleanup_session(session): # Access internals here, not in production code ...

**Gate:** Before adding methods to production code, ask "Is this only for tests?" Yes → Put in test utilities.
def cleanup_session(session): # Access internals here, not in production code ...

**检查点:** 在为生产代码添加方法之前,自问「这个方法仅用于测试吗?」如果是 → 移至测试工具类中。

Mocking Without Understanding

未理解依赖项就进行模拟

// BAD: Mock prevents side effect test actually needs
mock(database.save)  # Now duplicate detection won't work!

add_item(item)
add_item(item)  # Should fail as duplicate, but won't

// GOOD: Mock at correct level
mock(external_api.validate)  # Mock slow external call only

add_item(item)  # DB save works, duplicate detected
add_item(item)  # Fails correctly
// BAD: Mock prevents side effect test actually needs
mock(database.save)  # Now duplicate detection won't work!

add_item(item)
add_item(item)  # Should fail as duplicate, but won't

// GOOD: Mock at correct level
mock(external_api.validate)  # Mock slow external call only

add_item(item)  # DB save works, duplicate detected
add_item(item)  # Fails correctly

Incomplete Mocks

不完整的模拟对象

// BAD: Partial mock - missing fields downstream code needs
mock_response = {
    status: "success",
    data: {...}
    // Missing: metadata.request_id that downstream code uses
}

// GOOD: Mirror real API completely
mock_response = {
    status: "success",
    data: {...},
    metadata: {request_id: "...", timestamp: ...}
}
Gate: Before creating mocks, check "What does the real thing return?" Include ALL fields.
// BAD: Partial mock - missing fields downstream code needs
mock_response = {
    status: "success",
    data: {...}
    // Missing: metadata.request_id that downstream code uses
}

// GOOD: Mirror real API completely
mock_response = {
    status: "success",
    data: {...},
    metadata: {request_id: "...", timestamp: ...}
}
检查点: 在创建模拟对象之前,确认「真实的返回结果是什么?」包含所有字段。

TDD Prevents Anti-Patterns

TDD可预防反模式

  1. Write test first → Think about what you're testing (not mocks)
  2. Watch it fail → Confirms test tests real behavior
  3. Minimal implementation → No test-only methods creep in
  4. Real dependencies first → See what test needs before mocking
If testing mock behavior, you violated TDD - you added mocks without watching test fail against real code.
  1. 先编写测试 → 思考你要测试的内容(而非模拟对象)
  2. 观察测试失败 → 确认测试针对的是真实行为
  3. 最小化实现 → 不会混入仅测试用的方法
  4. 优先使用真实依赖 → 在添加模拟前先了解测试需求
如果测试模拟对象的行为,说明你违反了TDD原则 - 你在未观察测试针对真实代码失败的情况下就添加了模拟对象。

Language-Specific Patterns

特定语言模式

For detailed framework and language-specific patterns:
  • JavaScript/React: See
    references/javascript-react.md
    for React Testing Library queries, Jest/Vitest setup, Playwright E2E, and component testing patterns
  • Python: See
    references/python.md
    for pytest fixtures, polyfactory, respx mocking, testcontainers, and FastAPI testing
  • Go: See
    references/go.md
    for table-driven tests, testify/go-cmp assertions, testcontainers-go, and interface fakes
如需详细的框架和特定语言模式:
  • JavaScript/React: 查看
    references/javascript-react.md
    获取React Testing Library查询、Jest/Vitest配置、Playwright E2E和组件测试模式
  • Python: 查看
    references/python.md
    获取pytest fixtures、polyfactory、respx模拟、testcontainers和FastAPI测试相关内容
  • Go: 查看
    references/go.md
    获取表驱动测试、testify/go-cmp断言、testcontainers-go和接口fake相关内容

Quality Checklist

质量检查清单

Before completing tests, verify:
  • Happy path covered
  • Error conditions handled
  • Edge cases considered
  • Real dependencies used (minimal mocking)
  • Async waiting uses conditions, not arbitrary timeouts
  • Tests survive refactoring (no implementation details)
  • No test-only methods added to production code
  • No assertions on mock existence or call counts
  • Test names describe behavior, not implementation
完成测试前,请验证:
  • 覆盖正常流程
  • 处理错误场景
  • 考虑边界情况
  • 使用真实依赖(最少必要的模拟)
  • 异步等待使用条件而非任意超时
  • 测试可在重构后通过(无实现细节依赖)
  • 未向生产代码添加仅测试用的方法
  • 未断言模拟对象的存在或调用次数
  • 测试名称描述行为而非实现

What NOT to Test

无需测试的内容

  • Internal state
  • Private methods
  • Function call counts
  • Implementation details
  • Mock existence
  • Framework internals
Test behavior users/callers observe, not code structure.
  • 内部状态
  • 私有方法
  • 函数调用次数
  • 实现细节
  • 模拟对象的存在
  • 框架内部逻辑
测试用户/调用方可观测的行为,而非代码结构。

Quick Reference

快速参考

Test TypeWhenDependencies
IntegrationDefault choiceReal (test DB, real modules)
E2ECritical user workflowsReal (full stack)
UnitPure functions onlyNone
Anti-PatternFix
Testing mock existenceTest actual outcome instead
Test-only methods in productionMove to test utilities
Mocking without understandingUnderstand dependencies, mock minimally
Incomplete mocksMirror real API completely
Tests as afterthoughtTDD - write tests first
Arbitrary timeouts/sleepsUse condition-based waiting
<IMPORTANT> **Remember:** Behavior over implementation. Real over mocked. Outputs over internals. </IMPORTANT>
测试类型适用场景依赖项
集成测试默认选择真实依赖(测试数据库、真实模块)
E2E测试关键用户工作流真实依赖(全栈)
单元测试仅纯函数无依赖项
反模式修复方案
测试模拟对象的存在改为测试实际结果
生产代码中的仅测试方法移至测试工具类
未理解依赖就进行模拟理解依赖项,最小化模拟
不完整的模拟对象完全镜像真实API
测试作为事后补充TDD - 先编写测试
任意超时/sleep使用基于条件的等待
<IMPORTANT> **请记住:行为优先于实现。真实优先于模拟。输出优先于内部细节。** </IMPORTANT>