writing-tests
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWriting 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:
- Integration Tests (PRIMARY) - Multiple units with real dependencies
- E2E Tests (SECONDARY) - Complete workflows across the stack
- Unit Tests (RARE) - Pure functions only (no dependencies)
Default to integration tests. Only drop to unit tests for pure utility functions.
按照以下优先级编写测试:
- 集成测试(首要选择) - 多个单元,使用真实依赖项
- E2E测试(次要选择) - 覆盖全栈的完整工作流
- 单元测试(极少使用) - 仅用于纯函数(无依赖项)
默认使用集成测试。仅针对纯工具函数编写单元测试。
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:
- Review project standards - Check existing test files, testing docs, or project conventions
- Understand behavior - What should this do? What can go wrong?
- Choose test type - Integration (default), E2E (critical workflows), or Unit (pure functions)
- 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)编写测试前需完成:
- 评审项目标准 - 查看现有测试文件、测试文档或项目约定
- 理解行为逻辑 - 该功能应该实现什么?可能出现哪些故障?
- 选择测试类型 - 集成测试(默认)、E2E测试(关键工作流)或单元测试(纯函数)
- 识别依赖项 - 哪些需要使用真实依赖,哪些需要模拟?
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:
使用模拟前,请先自问:
- "What side effects does this method have?"
- "Does my test depend on those side effects?"
- If yes → Mock at lower level (the slow/external operation, not the method test needs)
- Unsure? → Run with real implementation first, observe what's needed, THEN add minimal mocking
- "该方法会产生哪些副作用?"
- "我的测试是否依赖这些副作用?"
- 如果是 → 在更低层级进行模拟(针对缓慢/外部操作,而非测试所需的方法)
- 不确定? → 先使用真实实现运行,观察需求,再添加最少必要的模拟
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, or arbitrary delayssetTimeout - 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 for detailed guidance on implementing proper condition polling and fixing flaky tests.
Skill(ce:condition-based-waiting)当测试涉及异步操作时,避免使用任意时长的超时:
// 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.
| Context | Assert On | Avoid |
|---|---|---|
| UI | Visible text, accessibility roles, user-visible state | CSS classes, internal state, test IDs |
| API | Response body, status code, headers | Internal DB state directly |
| CLI | stdout/stderr, exit code | Internal variables |
| Library | Return values, documented side effects | Private methods, internal state |
Why: Tests that assert on implementation details break when you refactor, even if behavior is unchanged.
原则:断言可观测的输出,而非内部状态。
| 场景 | 应断言的内容 | 应避免的内容 |
|---|---|---|
| UI | 可见文本、无障碍角色、用户可见状态 | CSS类、内部状态、测试ID |
| API | 响应体、状态码、请求头 | 直接断言内部数据库状态 |
| CLI | stdout/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) == 1Gate: 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 cleanupIn 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 correctlyIncomplete 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可预防反模式
- Write test first → Think about what you're testing (not mocks)
- Watch it fail → Confirms test tests real behavior
- Minimal implementation → No test-only methods creep in
- 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.
- 先编写测试 → 思考你要测试的内容(而非模拟对象)
- 观察测试失败 → 确认测试针对的是真实行为
- 最小化实现 → 不会混入仅测试用的方法
- 优先使用真实依赖 → 在添加模拟前先了解测试需求
如果测试模拟对象的行为,说明你违反了TDD原则 - 你在未观察测试针对真实代码失败的情况下就添加了模拟对象。
Language-Specific Patterns
特定语言模式
For detailed framework and language-specific patterns:
- JavaScript/React: See for React Testing Library queries, Jest/Vitest setup, Playwright E2E, and component testing patterns
references/javascript-react.md - Python: See for pytest fixtures, polyfactory, respx mocking, testcontainers, and FastAPI testing
references/python.md - Go: See for table-driven tests, testify/go-cmp assertions, testcontainers-go, and interface fakes
references/go.md
如需详细的框架和特定语言模式:
- JavaScript/React: 查看获取React Testing Library查询、Jest/Vitest配置、Playwright E2E和组件测试模式
references/javascript-react.md - Python: 查看获取pytest fixtures、polyfactory、respx模拟、testcontainers和FastAPI测试相关内容
references/python.md - Go: 查看获取表驱动测试、testify/go-cmp断言、testcontainers-go和接口fake相关内容
references/go.md
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 Type | When | Dependencies |
|---|---|---|
| Integration | Default choice | Real (test DB, real modules) |
| E2E | Critical user workflows | Real (full stack) |
| Unit | Pure functions only | None |
| Anti-Pattern | Fix |
|---|---|
| Testing mock existence | Test actual outcome instead |
| Test-only methods in production | Move to test utilities |
| Mocking without understanding | Understand dependencies, mock minimally |
| Incomplete mocks | Mirror real API completely |
| Tests as afterthought | TDD - write tests first |
| Arbitrary timeouts/sleeps | Use condition-based waiting |
| 测试类型 | 适用场景 | 依赖项 |
|---|---|---|
| 集成测试 | 默认选择 | 真实依赖(测试数据库、真实模块) |
| E2E测试 | 关键用户工作流 | 真实依赖(全栈) |
| 单元测试 | 仅纯函数 | 无依赖项 |
| 反模式 | 修复方案 |
|---|---|
| 测试模拟对象的存在 | 改为测试实际结果 |
| 生产代码中的仅测试方法 | 移至测试工具类 |
| 未理解依赖就进行模拟 | 理解依赖项,最小化模拟 |
| 不完整的模拟对象 | 完全镜像真实API |
| 测试作为事后补充 | TDD - 先编写测试 |
| 任意超时/sleep | 使用基于条件的等待 |