Testing Patterns
Systematic approach to effective testing. Use this skill when:
- •Writing or changing tests (load anti-patterns reference)
- •Designing test strategies for new features
- •Reviewing test coverage adequacy
- •Implementing test frameworks or infrastructure
Test-Driven Development (TDD)
TDD is MANDATORY for new feature code. Write tests before implementation.
The TDD Cycle
┌─────────────────────────────────────────┐ │ │ │ 1. RED → Write failing test │ │ 2. GREEN → Minimal code to pass │ │ 3. REFACTOR → Clean up, tests stay green │ │ 4. REPEAT │ │ │ └─────────────────────────────────────────┘
Why TDD?
| Benefit | How TDD Delivers |
|---|---|
| Prevents over-mocking | You see what test needs before mocking |
| No test-only production code | Minimal implementation = no extras |
| Tests real behavior | Failing test proves it tests something real |
| Better design | Testable code = loosely coupled code |
When TDD Applies
| Situation | TDD? | Notes |
|---|---|---|
| New features | ✅ Always | Core workflow |
| Behavior changes | ✅ Always | Modify test first, then code |
| Bug fixes | ✅ Preferred | Write test reproducing bug first |
| Pure refactors | ⚠️ Optional | Existing tests should cover |
| Exploratory spikes | ❌ Skip | But TDD rewrite after |
TDD Violations
If implementation arrives without tests:
- •Reject with "TDD Required"
- •Specify which tests should exist
- •Implementation writes tests first, then code
See references/testing-anti-patterns.md for detailed anti-patterns and gate functions.
Test Pyramid
/\
/ \ E2E Tests (10%)
/----\ Slow, expensive, few
/ \
/--------\ Integration Tests (20%)
/ \ Medium speed, focused
/------------\
/ \ Unit Tests (70%)
/________________\ Fast, isolated, many
Unit Tests
What: Test single function/class in isolation When: All business logic, utilities, data transformations Speed: Milliseconds Isolation: Mock external dependencies (DB, network, filesystem)
# Good unit test
def test_calculate_discount():
order = Order(items=[Item(price=100)])
assert order.calculate_discount(0.2) == 80
# Bad: tests integration, not unit
def test_order_discount():
db.create_order(...) # Touches database
api.apply_coupon(...) # External call
Integration Tests
What: Test component interactions When: Database queries, API contracts, service boundaries Speed: Seconds Isolation: Real dependencies for component under test
# Integration: tests DB interaction
def test_user_repository_finds_by_email():
repo = UserRepository(test_db)
repo.create(User(email="test@example.com"))
found = repo.find_by_email("test@example.com")
assert found.email == "test@example.com"
E2E Tests
What: Test full user workflows When: Critical paths, smoke tests, happy paths Speed: Minutes Isolation: None—tests complete system
# E2E: tests full flow
def test_user_can_checkout():
browser.goto("/")
browser.login("user@example.com", "password")
browser.add_to_cart("product-1")
browser.checkout()
assert browser.has_text("Order confirmed")
Coverage Strategy
What to Cover (Priority Order)
- •Business logic — revenue-impacting calculations
- •Security boundaries — auth, validation, access control
- •Error paths — exception handling, edge cases
- •Integration points — API contracts, DB queries
- •Happy paths — standard user workflows
What NOT to Prioritize
- •Getters/setters without logic
- •Framework code (already tested)
- •Third-party libraries
- •One-time scripts
- •UI layout (unless critical)
Coverage Targets
| Type | Target | Notes |
|---|---|---|
| Unit | 80%+ | Focus on logic, not coverage number |
| Integration | Critical paths | Don't test every permutation |
| E2E | Happy paths only | 5-10 core scenarios |
Edge Case Generation
Systematic Approach
For numeric inputs:
- •Zero
- •Negative numbers
- •Very large numbers (overflow)
- •Floating point precision
- •Boundary values (n-1, n, n+1)
For string inputs:
- •Empty string
- •Very long string
- •Unicode/emoji
- •Special characters
- •Whitespace only
- •SQL/HTML injection attempts
For collections:
- •Empty
- •Single element
- •Many elements
- •Duplicates
- •null/undefined elements
For dates:
- •Leap years
- •Timezone boundaries
- •DST transitions
- •Far past/future
- •Invalid dates
Example Matrix
| Input | Scenario | Expected | |-------|----------|----------| | price | 0 | Free item handling | | price | -5 | Validation error | | price | 999999.99 | Large number display | | name | "" | Required field error | | name | "a"*1000 | Truncation or error | | email | "test" | Invalid format error |
Mocking Patterns
When to Mock
| Context | Mock What? | Reason |
|---|---|---|
| Unit tests | External dependencies (DB, network, time) | Isolation + speed |
| Integration tests | External services only | Test real component interaction |
| E2E tests | Nothing | Test real system |
⚠️ TDD prevents over-mocking: If you write the test first and watch it fail, you know exactly what needs mocking.
Mock vs Stub vs Spy
# Stub: Returns canned response
payment_gateway = Mock()
payment_gateway.charge.return_value = {"status": "success"}
# Mock: Verifies interactions
email_service = Mock()
order.complete()
email_service.send.assert_called_once_with(
to="user@example.com",
subject="Order Confirmed"
)
# Spy: Wraps real implementation
real_logger = Logger()
spy_logger = Mock(wraps=real_logger)
# Calls real method but records calls
The Iron Laws of Mocking
- •NEVER test mock behavior — Use mocks to isolate your unit from dependencies, but assert on the unit's behavior, not the mock's existence. If your assertion is
expect(mockThing).toBeInTheDocument(), you're testing the mock, not the code. - •NEVER mock without understanding — Know side effects before isolating
- •NEVER create incomplete mocks — Mirror real API structure completely
Anti-Pattern Red Flags
- •Mock setup longer than test logic
- •Assertions on
*-mocktest IDs - •Can't explain why mock is needed
- •Mocking "just to be safe"
Full anti-pattern details: references/testing-anti-patterns.md
Test Quality Checklist
| Quality | Check |
|---|---|
| Readable | Can a new dev understand in 30 seconds? |
| Isolated | Does it fail independently of other tests? |
| Fast | Unit tests < 100ms, Integration < 5s? |
| Deterministic | Same result every run? |
| Focused | One assertion per test (or logical group)? |
| Maintainable | Will this break for wrong reasons? |
Test Naming
test_[unit]_[scenario]_[expected] test_calculateDiscount_withExpiredCoupon_returnsZero test_userRepository_findByEmail_whenNotFound_returnsNone test_checkout_withEmptyCart_showsError
See references/testing-frameworks.md for framework-specific guidance.