tdd-best-practices · v1.0.0 · 2026-08-28 · sha256 dc2804a692200c46
tdd-best-practices v1.0.0A
Immutable. This exact content is served forever at /api/v1/blob/dc2804a692200c46.
--- name: tdd-best-practices description: Test-Driven Development guidance. Use when writing tests before implementation, applying Red-Green-Refactor, testing Value Object contracts, typed domain failures/Results, DDD Aggregates, Domain Events/subscribers, transaction or cache boundaries, data migrations/backfills, Criteria/query adapters, repository contracts or persistence adapters, choosing between test doubles, deciding test granularity, reviewing test suite quality, or diagnosing brittle tests. license: MIT metadata: author: luckys version: "1.0.0" --- # TDD Best Practices Use this skill when the main question is how to drive implementation from tests, how to structure a test suite, or how to recover discipline in a codebase where tests were written after the fact. ## Working Style 1. Write one failing test, then make it pass with the minimum code needed. 2. Refactor only when all tests are green. 3. Test behaviors, not implementations — the public API, not private methods. 4. Keep tests as simple as the production code they verify. 5. A test suite that is hard to change is as expensive as production code that is hard to change. ## The Red-Green-Refactor Cycle ``` RED → Write a failing test that describes the next behavior. GREEN → Write the minimum code to make it pass. REFACTOR → Clean up both the code and the test — no new behavior. ``` The discipline is in the order. Never refactor on Red. On Green, add only the behavior demanded by the current failing test. ### The 3 Laws (Uncle Bob) 1. You may not write production code unless you have a failing unit test. 2. You may not write more of a unit test than is sufficient to fail. An intentionally missing public API/type can be a valid first Red; unrelated compilation or setup failures are not. 3. You may not write more production code than is sufficient to make the currently failing test pass. ## Design Workflow 1. **Describe the behavior** — what should the system do? Write the test name first. 2. **Write a failing test** — make it fail for the right reason (assertion, not setup error). 3. **Make it pass** — take the simplest path; you can clean up after. 4. **Refactor** — remove duplication, improve names, reduce complexity. 5. **Repeat** — the next test should be the smallest step forward. ## Choosing Test Granularity | Level | Tests | Speed | Confidence | |---|---|---|---| | Unit | Single class/function in isolation | Milliseconds | Behavior of one unit | | Integration | Multiple real collaborators | Seconds | Module boundaries work | | Acceptance / E2E | Full system from user perspective | Minutes | Feature works end-to-end | Start with units for logic-heavy code. Start with acceptance tests when following outside-in TDD. Integration tests fill the seams. ## Heuristics ### When to mock Mock or fake I/O boundaries in unit/application tests. Use real disposable infrastructure in adapter integration tests where SQL, collation, constraints, search analyzers, transactions, or protocol behavior are the subject. ### When testing an Aggregate Use real Value Objects and child Entities. Test commands through the root, including the rule that a rejected command leaves both state and pending events unchanged. ### When testing Domain Events Assert exact facts after Act: type/name, aggregate identity, business payload, count, and controlled metadata. Test subscriber handling separately from real-bus registration and prove durable duplicate/retry behavior with integration tests. ### When testing a Value Object Test the applicable public contract: semantic equality with distinct instances, exact invariant boundaries, and any exposed normalization, hashing, defensive-copy, operation, or serialization behavior. Inject clocks or policies instead of reading ambient context. ### When a test is too big Large setup is a signal to review fixture clarity and test scope, not proof that the test is too big. Split when multiple behaviors or failure reasons are coupled. ### When tests break on every refactor Tests are coupled to implementation, not behavior. Move the assertion to the public API surface. ### When you can't write a test first Treat difficulty writing a test first as a design or boundary signal; legacy constraints, framework coupling, or missing seams may require characterization tests before redesign. ## Warning Signs - Tests that only pass in a specific execution order. - Mocks that reproduce the production logic (over-mocking). - Tests with no assertion (`assertTrue(true)`). - Tests named after methods, not behaviors. - A test suite that takes more than 10 minutes to run on CI. - Tests that break when internal details change, not when behavior changes. ## References - Read `references/tdd-core-practices.md` for Red-Green-Refactor detail, FIRST properties, the test pyramid, triangulation, and baby steps. - Read `references/test-doubles.md` for the Meszaros taxonomy (Dummy, Fake, Stub, Spy, Mock), when to use each, and the classicist vs mockist distinction. - Read `references/tdd-schools.md` for the London (outside-in/mockist) vs Chicago (inside-out/classicist) schools, BDD, and ATDD. - Read `references/tdd-anti-patterns.md` for James Carr's 15 anti-patterns, Ian Cooper's "TDD, Where Did It All Go Wrong" insights, and recovery strategies. - Read `references/tdd-language-examples.md` for Red-Green-Refactor walkthroughs in TypeScript, Java, Python, C#, Ruby, and PHP with their respective test frameworks. - Read `references/projection-testing.md` for duplicate, ordering, concurrency, checkpoint, schema migration, replay/rebuild, and eventual-consistency tests for read models. - Read `references/migration-testing.md` for transformation, dirty-data, idempotent rerun, batch, checkpoint, concurrent-write, reconciliation, cutover, and recovery tests for data migrations. - Read `references/value-object-testing.md` for semantic equality, invariant boundaries, deep immutability, normalization, optionality, serialization, and property-based Value Object tests. - Read `references/aggregate-testing.md` for invariant-first Aggregate tests, repository contract and adapter tests, including atomic failures, deterministic Mothers, collection equality, transaction propagation, and concurrency integration tests. - Read `references/domain-event-testing.md` for Aggregate event assertions, application handoff, subscriber and real-bus tests, Outbox/Inbox delivery tests, Integration Event contracts, and CDC mapping tests. - Read `references/transaction-testing.md` for atomic commit/rollback, connection propagation, concurrency, cleanup, commit ambiguity, decorators, and side-effect boundary tests. - Read `references/cache-testing.md` for cache hit/miss contracts, invalidation, TTL, key isolation, corruption, failure degradation, stampede, and HTTP privacy tests. - Read `references/domain-error-testing.md` for typed failure assertions, Result short-circuit tests, exhaustive boundary mappings, public redaction, unknown 500 behavior, Effect execution, and error-test-double caveats. - Read `references/criteria-testing.md` for parser/AST tests, shared query semantics, real-adapter operator/security tests, pagination traversal, joins, nested boolean filters, and false-confidence warnings. ## Related Skills - Use `oop-best-practices` when a class is hard to test — the design needs improvement first. - Use `refactoring-best-practices` when adding tests to untested legacy code (characterization tests). - Use `ddd-best-practices` to decide Aggregate boundaries and invariant ownership before testing them. - Use `data-migration-best-practices` to design the operational migration workflow and cutover gates before testing it. ## Source Influences This skill is synthesized from: - *Test Driven Development: By Example* by Kent Beck - *Growing Object-Oriented Software, Guided by Tests* (GOOS) by Steve Freeman & Nat Pryce - *Working Effectively with Legacy Code* by Michael Feathers - *xUnit Test Patterns* by Gerard Meszaros - Ian Cooper — "TDD, Where Did It All Go Wrong" (talk, Vimeo) - James Carr — "TDD Anti-Patterns" (blog) - Tim Ottinger & Jeff Langr — "Unit Tests Are FIRST" (Pragmatic Bookshelf) - Dan North — "Introducing BDD" - [CodelyTV Aggregates course](https://github.com/CodelyTV/aggregates-course) (including testing counterexamples) - [CodelyTV Value Objects course](https://github.com/CodelyTV/value_objects-course) (including testing and TypeScript counterexamples) - [CodelyTV Repository Pattern course](https://github.com/CodelyTV/repository_pattern-course) (including repository-double and integration-test counterexamples) - [CodelyTV Domain Events course](https://github.com/CodelyTV/domain_modeling-domain_events-course) (including self-asserting-double and Event Bus coverage counterexamples) - [CodelyTV Domain Modeling Errors course](https://github.com/CodelyTV/domain_modeling-errors-course) (including Result, Effect, stale-contract, and mock counterexamples) - [CodelyTV Criteria Pattern course](https://github.com/CodelyTV/design_patterns-criteria-course) (including converter, pagination, injection, and test-coverage counterexamples) - [CodelyTV Infrastructure Design Transactions course](https://github.com/CodelyTV/infrastructure_design-transactions-course) (including transaction-scope, shared-connection, deferred-side-effect, and coverage counterexamples) - [CodelyTV Infrastructure Design Cache course](https://github.com/CodelyTV/infrastructure_design-cache-course) (including key-collision, invalidation, ETag, destructive-test, and self-asserting-double counterexamples) - [CodelyTV Four Rules of Simple Design course](https://github.com/CodelyTV/four_rules_of_simple_design-course) (including private-method coupling and passing repository-mock counterexamples)