M

Master Test Driven Development

All-in-one skill covering implementing, feature, bugfix, before. Includes structured workflows, validation checks, and reusable patterns for development.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

Master Test-Driven Development

An advanced TDD skill covering testing philosophy, test doubles, property-based testing, mutation testing, and strategies for applying TDD to complex systems including databases, APIs, and distributed architectures.

When to Use This Skill

Choose this skill when:

  • Practicing advanced TDD techniques beyond basic Red-Green-Refactor
  • Implementing test doubles (mocks, stubs, spies, fakes) correctly
  • Applying property-based testing for algorithmic code
  • Using mutation testing to verify test suite quality
  • Designing testable architectures from the ground up

Consider alternatives when:

  • Learning basic TDD → use a TDD workflow skill
  • Fixing existing failing tests → use a test-fixing skill
  • Setting up a test framework → use a testing patterns skill
  • Running tests in CI/CD → use a DevOps skill

Quick Start

// Advanced TDD: Property-Based Testing import { fc } from '@fast-check/vitest'; import { describe, it, expect } from 'vitest'; describe('sort function', () => { // Traditional example-based test it('sorts numbers ascending', () => { expect(sort([3, 1, 2])).toEqual([1, 2, 3]); }); // Property-based: output is always sorted it.prop([fc.array(fc.integer())])('output is always sorted', (arr) => { const sorted = sort(arr); for (let i = 1; i < sorted.length; i++) { expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i - 1]); } }); // Property: same elements, same length it.prop([fc.array(fc.integer())])('preserves all elements', (arr) => { const sorted = sort(arr); expect(sorted.length).toBe(arr.length); expect(sorted.sort()).toEqual([...arr].sort()); }); });

Core Concepts

Test Double Types

DoublePurposeBehaviorVerification
StubReturn canned responsesPredefined return valuesNo verification
MockVerify interactionsPredefined + expectationsVerify calls made
SpyRecord real callsWraps real implementationInspect call log
FakeWorking simplified versionIn-memory DB, fake serverVia behavior
DummyFill parameter listsNo behaviorNot used

Test Double Implementation

// Fake: Working in-memory implementation class FakeUserRepository implements UserRepository { private users = new Map<string, User>(); async findById(id: string): Promise<User | null> { return this.users.get(id) || null; } async save(user: User): Promise<void> { this.users.set(user.id, { ...user }); } async findByEmail(email: string): Promise<User | null> { return [...this.users.values()].find(u => u.email === email) || null; } } // Using the fake in tests (no mocking library needed) describe('UserService', () => { it('creates a user with hashed password', async () => { const repo = new FakeUserRepository(); const service = new UserService(repo); await service.createUser('[email protected]', 'password123'); const user = await repo.findByEmail('[email protected]'); expect(user).not.toBeNull(); expect(user!.password).not.toBe('password123'); // hashed }); });

Mutation Testing

# Run mutation testing with Stryker (JavaScript/TypeScript) npx stryker run # What it does: # 1. Makes small changes (mutations) to your source code # 2. Runs your tests against each mutation # 3. Reports which mutations survived (tests didn't catch them) # Example mutations: # - Replace > with >= (boundary mutation) # - Replace true with false (boolean mutation) # - Remove function call (statement deletion) # - Replace + with - (arithmetic mutation) # A survived mutation means your tests don't verify that behavior

Configuration

ParameterTypeDefaultDescription
testDoubleLibrarystring'vitest'Mocking: vitest built-in, sinon, or testdouble
propertyTestRunsnumber100Number of property-based test iterations
mutationThresholdnumber80Minimum mutation score percentage
coverageThresholdnumber90Minimum line coverage
testIsolationstring'strict'Isolation: strict (reset all) or relaxed
snapshotStrategystring'avoid'Snapshot testing: avoid, selective, or liberal

Best Practices

  1. Prefer fakes over mocks for collaborator testing — Fakes (in-memory implementations of interfaces) test real behavior without external dependencies. Mocks verify interaction patterns, which couples tests to implementation and breaks on refactoring.

  2. Use property-based testing for algorithms and data transformations — Example-based tests check specific cases; property-based tests verify invariants across thousands of random inputs. Properties like "sort preserves length" and "encode then decode equals original" catch edge cases you'd never think to write.

  3. Run mutation testing to validate your test suite — 100% code coverage can still miss bugs. Mutation testing modifies your code and checks if tests detect the changes. A high mutation score (>80%) means your assertions actually verify correctness.

  4. Test behavior through public interfaces, not internal methods — If a refactoring changes internal structure but preserves behavior, tests should still pass. Testing private methods creates brittle tests that break on every structural change.

  5. Use test data builders for complex object construction — Instead of constructing test objects with dozens of fields, create builder functions with sensible defaults. Override only the fields relevant to each test case.

Common Issues

Mock setup is longer than the test itself — Over-mocking indicates tight coupling in production code. Refactor to depend on interfaces with simple contracts. If a function needs 5 mocks, it has too many dependencies.

Property-based tests find bugs but are hard to reproduce — Most property testing libraries print the seed and shrunk example on failure. Save these and add them as explicit regression tests. This gives you the best of both worlds: discovery and reproducibility.

Mutation score is low despite high coverage — Coverage measures which lines execute, not what's verified. Add assertions for return values, state changes, and error conditions. Every conditional branch should have a test that fails if the condition is removed.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates