Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
TDD — Test-First Execution
The Iron Law
No production code without a failing test first. This is enforcement, not advice:
- Code written before its test gets deleted and redone test-first. Not "test added after" — deleted, then rebuilt from the test.
- A test that passes on first run is wrong. You never saw it fail, so you don't know it can fail. See the red before you make the green.
Step 0 — whose code is this?
Touching untested legacy code? STOP. Run safe-incremental-coding to build the characterization net first, then return here. That skill deliberately opposes the Iron Law (no fine-grained TDD tests on legacy structure — they bake in the bad shape), which is exactly why it is a separate skill. New code, or code with tests you can stand on, proceeds directly.
The loop
Run one loop per increment of behavior. Keep each pass small enough that you could throw it away without grief.
- Frame the smallest reversible move. One caller-visible increment of behavior — the cheapest reversible learning step, smaller than feels natural. If you can't state the outcome in a sentence, the step is too big; split it. Say it while choosing: "the smallest reversible move here is…".
- Write the test first, predict the failure, see it fail. Express what the code should do from its caller's perspective. Say the expected failure out loud before running — a different failure already taught you something.
- Get to green the simplest way. The least code that passes; naive is fine. This is a tactical step, not a design step — don't polish yet.
- Refactor under green. Improve a concrete design problem, then run the affected tests before the next behavior change. Use
clean-codeto name a smell when one exists. Assert internal invariants where they must hold: an assertion is executable documentation, and its failure is a bug rather than a condition to catch and ignore. - Integrate coherent increments. Keep each increment small and passing. Commit only when the user or caller has authorized it; use completed behavior and passing checks to choose the boundary, not a timer.
Control the variables
You can only learn from a step if you can attribute its effect:
- One change at a time. Never bundle a refactor with a behavior change with a config tweak.
- An intermittent test is a failure, never a pass to be re-run.
- Avoid programming by coincidence. If the code passes, know why.
- Never game the check. Deleting, skipping, weakening, narrowing, or mocking-away a test to reach green is forbidden; if the test or contract is wrong, stop and report it (
shared/references/engineering-rules.md, Contract integrity).
The test-writing bar
Write-time rules. test-lens is the judge when an existing test's value is in question; these are the standards you write to so it never has to convict you.
- Style ranking: output-based (assert a pure function's return) > state-based > communication-based (mocks). Push code toward output-based with a functional core (pure decisions) wrapped in a mutable shell (thin I/O glue).
- Assert observable behavior, never implementation details. Never assert calls to a stub.
- AAA — Arrange / Act / Assert, one of each — named as a domain statement of behavior:
delivery_with_a_past_date_is_invalid, nottestIsValid_case3. - A test that's hard to write is a design smell — fix the design, not the test.
- Refactoring litmus: if the implementation were swapped for a completely different one, would this test still be valid? It should be.
Design and risk cues
Use these cues while choosing tests and refactoring; they do not require another review pass before each integration.
- Farley's five levers: modularity, cohesion, separation of concerns, abstraction, low coupling. Address a regression in these properties within the changed scope.
- "What happens if…?": select the failure cases exposed by this behavior, such as invalid input, dependency failure, concurrency, security, or money loss. Reuse cases and decisions already present in the acceptance contract.
Per-step output contract
After each loop iteration (or coherent batch), report:
changed: what was edited.behavior: what the code now does that it didn't before, or that behavior was preserved.risk_guarded: the main design or data risk this step's test now guards.verification: tests or checks run, or why they could not run.
Routing
- Stored state, queues, retries, migrations, external APIs → reuse the approved data-systems-coding-lens findings. Run the lens only for an uncovered data risk or a changed design surface, not before every loop iteration.
- Smell vocabulary and naming during the refactor step →
clean-code. - Judging whether an existing test is worth keeping →
test-lens. - An unexplained failure surfaces mid-loop →
diagnose. An understood red or regression stays in the current loop; use the existing evidence instead of starting a second investigation.
Gotchas
- Monster steps — a half-day of code before the first test run.
- Test-after — writing the test to fit code you already wrote; you lose the design feedback, and the Iron Law says delete and redo.
- Coverage-chasing — coverage is a side effect of the loop, never the target.
- Gold-plating in green — designing while unsafe; design belongs in the refactor step.
- "No time to test" — there is no speed-vs-quality trade-off; the way to go faster is to keep rework low.
- Do not expand scope to clean unrelated code.
- Do not add layers, services, or helpers because they sound tidy.
- Do not weaken harmless local coupling when the cure adds more indirection than clarity.
- When the design needs to turn, steer green-to-green in small refactoring steps — changing your mind has a cost; that bill is the speed small steps bought you earlier.
- Do not quote or reconstruct source text from the books this skill distills.

