testing-patterns

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.

$ 安裝

git clone https://github.com/groupzer0/vs-code-agents /tmp/vs-code-agents && cp -r /tmp/vs-code-agents/vs-code-agents/skills/testing-patterns ~/.claude/skills/vs-code-agents

// tip: Run this command in your terminal to install the skill


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?

BenefitHow TDD Delivers
Prevents over-mockingYou see what test needs before mocking
No test-only production codeMinimal implementation = no extras
Tests real behaviorFailing test proves it tests something real
Better designTestable code = loosely coupled code

When TDD Applies

SituationTDD?Notes
New features✅ AlwaysCore workflow
Behavior changes✅ AlwaysModify test first, then code
Bug fixes✅ PreferredWrite test reproducing bug first
Pure refactors⚠️ OptionalExisting tests should cover
Exploratory spikes❌ SkipBut TDD rewrite after

TDD Violations

If implementation arrives without tests:

  1. Reject with "TDD Required"
  2. Specify which tests should exist
  3. 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)

  1. Business logic — revenue-impacting calculations
  2. Security boundaries — auth, validation, access control
  3. Error paths — exception handling, edge cases
  4. Integration points — API contracts, DB queries
  5. 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

TypeTargetNotes
Unit80%+Focus on logic, not coverage number
IntegrationCritical pathsDon't test every permutation
E2EHappy paths only5-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

ContextMock What?Reason
Unit testsExternal dependencies (DB, network, time)Isolation + speed
Integration testsExternal services onlyTest real component interaction
E2E testsNothingTest 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

  1. 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.
  2. NEVER mock without understanding — Know side effects before isolating
  3. NEVER create incomplete mocks — Mirror real API structure completely

Anti-Pattern Red Flags

  • Mock setup longer than test logic
  • Assertions on *-mock test 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

QualityCheck
ReadableCan a new dev understand in 30 seconds?
IsolatedDoes it fail independently of other tests?
FastUnit tests < 100ms, Integration < 5s?
DeterministicSame result every run?
FocusedOne assertion per test (or logical group)?
MaintainableWill 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.