test-first-thinking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Test-First Thinking

Test-First Thinking

Overview

概述

Test-first thinking is a design discipline that requires thinking about features, testability, and maintainability BEFORE writing implementation code. This is not strict TDD (Test-Driven Development), but rather a mental model that ensures you design simple, testable interfaces from the start.
Core Principle: If you can't easily describe what tests you'd write, your design may be too complex.
Test-first thinking是一种设计规范,要求在编写实现代码之前,先思考功能、可测试性和可维护性。这并非严格意义上的TDD(Test-Driven Development,测试驱动开发),而是一种思维模式,确保你从一开始就设计出简洁、可测试的接口。
核心原则:如果你无法轻松描述自己要编写的测试用例,那么你的设计可能过于复杂。

When to Use This Skill

何时使用该思维方法

Explicit Triggers

明确触发场景

  • "Think about tests first"
  • "Design for testability"
  • "What tests do I need?"
  • "Use test-first approach"
  • "TDD thinking"
  • "How should I test this?"
  • “先考虑测试”
  • “为可测试性而设计”
  • “我需要哪些测试?”
  • “采用测试优先方法”
  • “TDD思维”
  • “我应该如何测试这个功能?”

Implicit Triggers

隐含触发场景

  • Before creating a new class or method
  • Before refactoring existing code
  • When starting a new feature implementation
  • When reviewing code that lacks tests
  • When design feels overly complex
  • 创建新类或方法之前
  • 重构现有代码之前
  • 启动新功能开发之前
  • 评审缺少测试的代码时
  • 感觉设计过于复杂时

Debugging Triggers

调试触发场景

  • Tests are difficult to write for existing code
  • Code requires extensive mocking to test
  • Implementation has grown too complex
  • Edge cases keep surfacing after deployment
  • 现有代码的测试用例难以编写
  • 代码需要大量模拟(Mock)才能测试
  • 实现逻辑变得过于复杂
  • 上线后不断出现边缘情况问题

What This Skill Does

该思维方法的作用

This skill guides you through a pre-implementation checklist that ensures:
  1. Feature enumeration - List all expected behaviors before coding
  2. Simplicity check - Consider maintainability and complexity
  3. Test identification - Know what tests validate each behavior
  4. Interface design - Create signatures that make testing easy
  5. Edge case awareness - Think through error conditions upfront
该方法会引导你完成一套编码前的检查清单,确保:
  1. 功能枚举 - 编码前列出所有预期行为
  2. 简洁性检查 - 考量可维护性和复杂度
  3. 测试用例识别 - 明确验证每个行为的测试内容
  4. 接口设计 - 设计便于测试的签名
  5. 边缘情况预判 - 提前考虑错误处理场景

The Test-First Checklist

Test-First检查清单

Run through this checklist BEFORE writing implementation code:
在编写实现代码之前,完成以下检查:

1. Enumerate Features and Behaviors

1. 枚举功能与行为

Ask yourself:
  • What should this class/method do?
  • What are the expected inputs and outputs?
  • What transformations or side effects occur?
Example:
python
undefined
自问:
  • 这个类/方法应该实现什么功能?
  • 预期的输入和输出是什么?
  • 会发生哪些转换或副作用?
示例:
python
undefined

Before implementing UserRegistration class, list features:

在实现UserRegistration类之前,先列出功能:

1. Validate email format

1. 验证邮箱格式

2. Check if email already exists

2. 检查邮箱是否已存在

3. Hash password securely

3. 安全哈希密码

4. Store user in database

4. 将用户信息存储到数据库

5. Send confirmation email

5. 发送确认邮件

6. Return success/failure result

6. 返回成功/失败结果

undefined
undefined

2. Consider Edge Cases and Error Conditions

2. 考虑边缘情况与错误场景

Ask yourself:
  • What can go wrong?
  • How should errors be handled?
  • What are the boundary conditions?
Example:
python
undefined
自问:
  • 可能会出现哪些问题?
  • 应该如何处理错误?
  • 边界条件有哪些?
示例:
python
undefined

Edge cases for UserRegistration:

UserRegistration的边缘情况:

- Invalid email format

- 无效邮箱格式

- Duplicate email

- 重复邮箱

- Weak password

- 弱密码

- Database connection failure

- 数据库连接失败

- Email service unavailable

- 邮件服务不可用

- Null/empty inputs

- 空值/空输入

undefined
undefined

3. Identify Required Tests

3. 确定所需测试用例

Ask yourself:
  • What test cases validate each feature?
  • How do I verify error handling?
  • What mocks/fixtures are needed?
Example:
python
undefined
自问:
  • 哪些测试用例可以验证每个功能?
  • 如何验证错误处理逻辑?
  • 需要哪些模拟对象/测试夹具?
示例:
python
undefined

Tests needed for UserRegistration:

UserRegistration需要的测试用例:

- test_valid_registration_succeeds()

- test_valid_registration_succeeds()

- test_invalid_email_raises_validation_error()

- test_invalid_email_raises_validation_error()

- test_duplicate_email_returns_failure()

- test_duplicate_email_returns_failure()

- test_weak_password_raises_validation_error()

- test_weak_password_raises_validation_error()

- test_database_failure_returns_failure()

- test_database_failure_returns_failure()

- test_email_service_failure_logs_warning()

- test_email_service_failure_logs_warning()

- test_null_inputs_raise_value_error()

- test_null_inputs_raise_value_error()

undefined
undefined

4. Design for Testability

4. 为可测试性而设计

Ask yourself:
  • Does this interface make testing easy?
  • Can I test without complex mocking?
  • Are dependencies explicit and injectable?
  • Is the function pure (no hidden side effects)?
Good Design (Testable):
python
def register_user(
    email: str,
    password: str,
    user_repo: UserRepository,
    email_service: EmailService
) -> Result[User, RegistrationError]:
    """Register new user with explicit dependencies."""
    # Dependencies are injected - easy to mock
    # Returns Result type - easy to test both paths
    # Pure function - predictable behavior
Bad Design (Hard to Test):
python
def register_user(email: str, password: str) -> None:
    """Register new user."""
    # Hidden dependency on global database connection
    # Hidden dependency on email service
    # No return value - can't verify success
    # Side effects make testing difficult
自问:
  • 这个接口是否便于测试?
  • 能否无需复杂模拟即可测试?
  • 依赖是否明确且可注入?
  • 函数是否为纯函数(无隐藏副作用)?
良好设计(可测试):
python
def register_user(
    email: str,
    password: str,
    user_repo: UserRepository,
    email_service: EmailService
) -> Result[User, RegistrationError]:
    """使用显式依赖注册新用户。"""
    # 依赖可注入 - 便于模拟
    # 返回Result类型 - 便于测试成功/失败路径
    # 纯函数 - 行为可预测
糟糕设计(难以测试):
python
def register_user(email: str, password: str) -> None:
    """注册新用户。"""
    # 依赖全局数据库连接(隐藏)
    # 依赖邮件服务(隐藏)
    # 无返回值 - 无法验证是否成功
    # 副作用导致测试难度大

5. Look at Existing Tests First

5. 先查看现有测试用例

When editing existing code:
  1. Read the test file BEFORE modifying implementation
  2. Existing tests show what features the code should have
  3. If tests are missing, write them first
  4. If tests are hard to understand, the code is likely too complex
Example:
bash
undefined
修改现有代码时:
  1. 修改实现之前先阅读测试文件
  2. 现有测试用例会展示代码应具备的功能
  3. 如果缺少测试用例,先编写测试
  4. 如果测试用例难以理解,说明代码可能过于复杂
示例:
bash
undefined

Before editing src/auth/registration.py:

修改src/auth/registration.py之前:

1. Read tests/unit/auth/test_registration.py

1. 阅读tests/unit/auth/test_registration.py

2. Understand what behaviors are tested

2. 理解已测试的行为

3. Identify what's NOT tested (gaps)

3. 识别未测试的内容(测试缺口)

4. Add tests for new behavior

4. 为新行为添加测试用例

5. THEN modify implementation

5. 之后再修改实现代码

undefined
undefined

6. Implement

6. 开始实现

Only after completing steps 1-5:
  • Write the implementation
  • Run tests continuously as you code
  • Refactor based on test feedback
  • Add tests if new edge cases emerge
仅在完成步骤1-5后:
  • 编写实现代码
  • 编码过程中持续运行测试
  • 根据测试反馈进行重构
  • 如果出现新的边缘情况,补充测试用例

Quick Reference: Red Flags

快速参考:危险信号

Stop and reconsider if you encounter:
  • "I'll write tests later" - Write tests now or redesign
  • "This needs extensive mocking" - Dependencies may be too coupled
  • "I can't describe what tests I'd write" - Design is too complex
  • "Tests would be too complicated" - Implementation is too complicated
  • "This is hard to test" - This is hard to maintain
  • "I need to mock everything" - Too many dependencies
  • "Tests keep breaking" - Implementation is too fragile
如果遇到以下情况,请暂停并重新考量:
  • “我之后再写测试” - 现在就写测试或重新设计
  • “这需要大量模拟” - 依赖可能耦合过紧
  • “我无法描述要写的测试用例” - 设计过于复杂
  • “测试用例会太复杂” - 实现逻辑过于复杂
  • “这很难测试” - 这也会很难维护
  • “我需要模拟所有依赖” - 依赖过多
  • “测试用例不断失败” - 实现逻辑过于脆弱

Benefits

收益对比

AspectBefore Test-First ThinkingAfter Test-First Thinking
Design ComplexityGrows organically, becomes tangledKept simple by testability constraint
Test CoverageWritten after (if at all), incompleteDesigned in from start, comprehensive
Edge CasesDiscovered in productionIdentified during design
Debugging TimeHigh - complex interactionsLow - isolated, testable units
Refactoring ConfidenceLow - fear of breaking thingsHigh - tests verify behavior
Maintenance CostHigh - difficult to changeLow - clear contracts and tests
维度采用Test-First思维之前采用Test-First思维之后
设计复杂度自然增长,逐渐混乱受可测试性约束,保持简洁
测试覆盖率编码后才写(甚至不写),覆盖不全从设计阶段就纳入,覆盖全面
边缘情况上线后才发现设计阶段就已识别
调试时间长 - 交互逻辑复杂短 - 单元独立、可测试
重构信心低 - 担心破坏现有功能高 - 测试用例可验证行为
维护成本高 - 难以修改低 - 契约清晰、有测试保障

Integration with Quality Gates

与质量门禁的集成

This skill supports:
  • quality-run-quality-gates - Ensures tests exist before marking complete
  • quality-capture-baseline - Requires test coverage metrics
  • quality-detect-regressions - Verifies tests pass consistently
  • test-debug-failures - Makes test failures easier to diagnose
该思维方法支持:
  • quality-run-quality-gates - 确保标记完成前已存在测试用例
  • quality-capture-baseline - 要求提供测试覆盖率指标
  • quality-detect-regressions - 验证测试用例持续通过
  • test-debug-failures - 让测试失败的诊断更简单

Expected Outcomes

预期结果

Success

成功案例

Before implementing PaymentProcessor class:

Features enumerated:
✅ Process credit card payment
✅ Validate payment amount
✅ Handle payment gateway response
✅ Store transaction record
✅ Send receipt email

Edge cases identified:
✅ Invalid card number
✅ Insufficient funds
✅ Gateway timeout
✅ Network failure
✅ Duplicate transaction

Tests identified:
✅ test_valid_payment_succeeds()
✅ test_invalid_card_raises_error()
✅ test_insufficient_funds_returns_failure()
✅ test_gateway_timeout_retries()
✅ test_duplicate_transaction_prevented()

Interface designed:
✅ Dependencies injected (gateway, transaction_repo)
✅ Returns Result type for error handling
✅ Pure function - no hidden state
✅ Easy to mock gateway for testing

Ready to implement with confidence!
在实现PaymentProcessor类之前:

已枚举功能:
✅ 处理信用卡支付
✅ 验证支付金额
✅ 处理支付网关响应
✅ 存储交易记录
✅ 发送收款邮件

已识别边缘情况:
✅ 无效卡号
✅ 余额不足
✅ 网关超时
✅ 网络故障
✅ 重复交易

已确定测试用例:
✅ test_valid_payment_succeeds()
✅ test_invalid_card_raises_error()
✅ test_insufficient_funds_returns_failure()
✅ test_gateway_timeout_retries()
✅ test_duplicate_transaction_prevented()

已完成接口设计:
✅ 依赖可注入(网关、交易仓库)
✅ 返回Result类型用于错误处理
✅ 纯函数 - 无隐藏状态
✅ 便于模拟网关进行测试

可以满怀信心地开始实现了!

Failure (Redesign Needed)

失败案例(需要重新设计)

Before implementing ReportGenerator class:

Attempted to list features:
❌ "Generate reports" - too vague
❌ "Process data" - what data? how?
❌ Multiple responsibilities identified
❌ Can't describe specific behaviors

Attempted to identify tests:
❌ "Test that it works" - not specific enough
❌ Would need to mock 15+ dependencies
❌ No clear success/failure paths
❌ Can't isolate behaviors for testing

Red flags:
❌ Design too complex
❌ Unclear responsibilities
❌ Too many dependencies
❌ Not testable in current form

Action: Break into smaller, focused classes:
- ReportDataFetcher (single responsibility)
- ReportFormatter (single responsibility)
- ReportExporter (single responsibility)

Retry test-first thinking for each class individually.
在实现ReportGenerator类之前:

尝试列出功能:
❌ “生成报告” - 过于模糊
❌ “处理数据” - 处理什么数据?如何处理?
❌ 识别出多个职责
❌ 无法描述具体行为

尝试确定测试用例:
❌ “测试它能正常工作” - 不够具体
❌ 需要模拟15个以上依赖
❌ 没有清晰的成功/失败路径
❌ 无法隔离行为进行测试

危险信号:
❌ 设计过于复杂
❌ 职责不明确
❌ 依赖过多
❌ 当前形式无法测试

行动方案:拆分为更小、职责单一的类:
- ReportDataFetcher(单一职责)
- ReportFormatter(单一职责)
- ReportExporter(单一职责)

为每个类单独重新应用Test-First思维方法。

Notes

注意事项

  1. This is NOT strict TDD - You don't have to write tests first, but you must THINK about tests first
  2. Mental model matters - The discipline of considering testability improves design
  3. Start simple - If you can't explain it simply, you don't understand it well enough
  4. Tests reveal design flaws - Hard to test = hard to maintain
  5. Iterate - If tests are difficult, redesign the interface
  6. Use existing tests as documentation - They show what the code should do
  7. Testability IS maintainability - They're the same thing
  1. 这并非严格的TDD - 你不需要先写测试代码,但必须先思考测试用例
  2. 思维模式很重要 - 考量可测试性的习惯能提升设计质量
  3. 从简出发 - 如果你无法简单解释,说明你理解得还不够透彻
  4. 测试用例会暴露设计缺陷 - 难以测试 = 难以维护
  5. 迭代优化 - 如果测试难度大,重新设计接口
  6. 将现有测试用例视为文档 - 它们会展示代码应具备的功能
  7. 可测试性即可维护性 - 二者是统一的

Supporting Files

配套文件

This skill is intentionally minimal - it's a thinking discipline, not a complex workflow. No additional scripts or references are needed.
该方法刻意保持简洁 - 它是一种思维规范,而非复杂的工作流。无需额外脚本或参考资料。