TDD workflow and test strategy patterns including test pyramid, coverage strategies, mocking approaches, and anti-patterns. Load when writing tests, designing test strategies, or reviewing test coverage.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
name: testing-patterns description: TDD workflow and test strategy patterns including test pyramid, coverage strategies, mocking approaches, and anti-patterns. Load when writing tests, designing test strategies, or reviewing test coverage. license: MIT metadata: author: groupzer0 version: "1.1"
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.
More by groupzer0
View allUnified document lifecycle management. Defines terminal statuses, unified numbering via .next-id, close procedures, and orphan detection. Load at session start.
Version management, release verification, and deployment procedures for software releases. Includes semver guidance, version consistency checks, and platform-specific constraints.
Core software engineering principles (SOLID, DRY, YAGNI, KISS) with detection patterns and refactoring guidance. Load when reviewing code quality, planning architecture, or identifying technical debt.
Security vulnerability detection patterns including OWASP Top 10, language-specific vulnerabilities, and remediation guidance. Load when reviewing code for security issues, conducting audits, or implementing authentication/authorization.