test-first-thinking
npx skills add https://github.com/dawiddutoit/custom-claude --skill test-first-thinking
Agent 安装分布
Skill 文档
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.
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?”
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
What This Skill Does
This skill guides you through a pre-implementation checklist that ensures:
- Feature enumeration – List all expected behaviors before coding
- Simplicity check – Consider maintainability and complexity
- Test identification – Know what tests validate each behavior
- Interface design – Create signatures that make testing easy
- Edge case awareness – Think through error conditions upfront
The Test-First Checklist
Run through this checklist BEFORE writing implementation code:
1. Enumerate Features and Behaviors
Ask yourself:
- What should this class/method do?
- What are the expected inputs and outputs?
- What transformations or side effects occur?
Example:
# Before implementing UserRegistration class, list features:
# 1. Validate email format
# 2. Check if email already exists
# 3. Hash password securely
# 4. Store user in database
# 5. Send confirmation email
# 6. Return success/failure result
2. Consider Edge Cases and Error Conditions
Ask yourself:
- What can go wrong?
- How should errors be handled?
- What are the boundary conditions?
Example:
# Edge cases for UserRegistration:
# - Invalid email format
# - Duplicate email
# - Weak password
# - Database connection failure
# - Email service unavailable
# - Null/empty inputs
3. Identify Required Tests
Ask yourself:
- What test cases validate each feature?
- How do I verify error handling?
- What mocks/fixtures are needed?
Example:
# Tests needed for UserRegistration:
# - test_valid_registration_succeeds()
# - test_invalid_email_raises_validation_error()
# - test_duplicate_email_returns_failure()
# - test_weak_password_raises_validation_error()
# - test_database_failure_returns_failure()
# - test_email_service_failure_logs_warning()
# - test_null_inputs_raise_value_error()
4. Design for Testability
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):
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):
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
5. Look at Existing Tests First
When editing existing code:
- Read the test file BEFORE modifying implementation
- Existing tests show what features the code should have
- If tests are missing, write them first
- If tests are hard to understand, the code is likely too complex
Example:
# Before editing src/auth/registration.py:
# 1. Read tests/unit/auth/test_registration.py
# 2. Understand what behaviors are tested
# 3. Identify what's NOT tested (gaps)
# 4. Add tests for new behavior
# 5. THEN modify implementation
6. Implement
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
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
| Aspect | Before Test-First Thinking | After Test-First Thinking |
|---|---|---|
| Design Complexity | Grows organically, becomes tangled | Kept simple by testability constraint |
| Test Coverage | Written after (if at all), incomplete | Designed in from start, comprehensive |
| Edge Cases | Discovered in production | Identified during design |
| Debugging Time | High – complex interactions | Low – isolated, testable units |
| Refactoring Confidence | Low – fear of breaking things | High – tests verify behavior |
| Maintenance Cost | High – difficult to change | Low – clear contracts and tests |
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
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!
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.
Notes
- This is NOT strict TDD – You don’t have to write tests first, but you must THINK about tests first
- Mental model matters – The discipline of considering testability improves design
- Start simple – If you can’t explain it simply, you don’t understand it well enough
- Tests reveal design flaws – Hard to test = hard to maintain
- Iterate – If tests are difficult, redesign the interface
- Use existing tests as documentation – They show what the code should do
- Testability IS maintainability – They’re the same thing
Supporting Files
This skill is intentionally minimal – it’s a thinking discipline, not a complex workflow. No additional scripts or references are needed.