test-driven-development · git:20260501.88e4ec8 · 2026-05-01 · sha256 714509918e831732

test-driven-development git:20260501.88e4ec8A

Immutable. This exact content never changes and is served at /api/v1/blob/714509918e831732.

---
name: test-driven-development
description: Use when implementing any feature or bugfix, before writing implementation code
---

<skill>

  <purpose>
    Write the test first. Watch it fail. Write minimal code to pass. If you didn't watch the test fail, you don't know if it tests the right thing.
  </purpose>

  <triggers>
    <rule>Use when implementing any new feature, before writing implementation code.</rule>
    <rule>Use when fixing any bug — write a failing test that reproduces it first.</rule>
    <rule>Use when refactoring or changing behavior in existing code.</rule>
  </triggers>

  <warning level="hard">
    NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST. Violating the letter of the rules is violating the spirit. Write code before the test? Delete it. Start over. No exceptions without your human partner's permission.
  </warning>

  <rules>
    <rule>Test-first is non-negotiable. RED before GREEN.</rule>
    <rule>Wrote code before the test? Delete it. Don't keep it as "reference", don't "adapt" it, don't look at it. Delete means delete. Implement fresh from tests.</rule>
    <rule>Exceptions (ask your human partner first): throwaway prototypes, generated code, configuration files.</rule>
  </rules>

  <process>
    <step>**RED** — Write one minimal failing test showing what should happen. One behavior, clear name, real code (no mocks unless unavoidable).</step>
    <step>**Verify RED** — Run `npm test path/to/test.test.ts`. Confirm: test *fails* (not errors), failure message is expected, fails because feature is missing (not typos). Test passes immediately? You're testing existing behavior — fix the test.</step>
    <step>**GREEN** — Write the simplest code to pass the test. Don't add features, refactor other code, or "improve" beyond the test.</step>
    <step>**Verify GREEN** — Run tests. Confirm: test passes, *all other* tests still pass, output is pristine (no errors, warnings). Test fails? Fix code, not test. Other tests fail? Fix now.</step>
    <step>**REFACTOR** — After green only: remove duplication, improve names, extract helpers. Keep tests green. Don't add behavior.</step>
    <step>**Repeat** — Next failing test for next behavior.</step>
  </process>

  <flow format="dot">
    ```dot
    digraph tdd_cycle {
      rankdir=LR;
      red [label="RED\nFailing test", shape=box, style=filled, fillcolor="#ffcccc"];
      green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
      refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
      red -> green -> refactor -> red;
    }
    ```
  </flow>

  <example type="good">
    **RED** — Bug: empty email accepted
    ```typescript
    test('rejects empty email', async () => {
      const result = await submitForm({ email: '' });
      expect(result.error).toBe('Email required');
    });
    ```
    **Verify RED** — `FAIL: expected 'Email required', got undefined`

    **GREEN**
    ```typescript
    function submitForm(data: FormData) {
      if (!data.email?.trim()) return { error: 'Email required' };
      // ...
    }
    ```
    **Verify GREEN** — `PASS`

    **REFACTOR** — Extract validation for multiple fields if needed.
  </example>

  <example type="bad">
    Writing production code first, then writing tests that pass immediately. Passing immediately proves nothing — you never saw the test catch the bug.
  </example>

  <antipattern>
    "I'll write tests after to verify it works" — Tests-after answer "What does this do?" Tests-first answer "What *should* this do?" Tests-after are biased by your implementation. You test what you built, not what's required.
  </antipattern>

  <antipattern>
    "Keep as reference, write tests first" — You'll adapt it. That's testing after. Delete means delete.
  </antipattern>

  ## Common Rationalizations

  | Excuse | Reality |
  |--------|---------|
  | "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
  | "I'll test after" | Tests passing immediately prove nothing. |
  | "Already manually tested" | Ad-hoc != systematic. No record, can't re-run. |
  | "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is tech debt. |
  | "Need to explore first" | Fine. Throw away exploration, start with TDD. |
  | "Test hard = design unclear" | Listen to the test. Hard to test = hard to use. |
  | "TDD will slow me down" | TDD is faster than debugging. |
  | "This is different because..." | No. Start over with TDD. |

  ## Red Flags — STOP and Start Over

  - Code before test
  - Test passes immediately
  - Can't explain why test failed
  - Tests added "later"
  - Rationalizing "just this once"
  - "It's about spirit not ritual"

  **All of these mean: Delete code. Start over with TDD.**

  ## When Stuck

  | Problem | Solution |
  |---------|----------|
  | Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
  | Test too complicated | Design too complicated. Simplify interface. |
  | Must mock everything | Code too coupled. Use dependency injection. |

  ## Verification Checklist

  Before marking work complete:

  - [ ] Every new function/method has a test
  - [ ] Watched each test fail before implementing
  - [ ] Each test failed for expected reason (feature missing, not typo)
  - [ ] Wrote minimal code to pass each test
  - [ ] All tests pass with pristine output
  - [ ] Tests use real code (mocks only if unavoidable)
  - [ ] Edge cases and errors covered

  Can't check all boxes? You skipped TDD. Start over.

  <see file="testing-anti-patterns.md"/>

</skill>