Skip to content
Testing
Skill

/tdd

Enforces strict test-driven development. Use when implementing ANY feature, bugfix, or refactor — before writing implementation code. Also use when someone says 'add tests', 'write tests', 'test this', 'TDD', or when you're about to write production code of any kind. If you're

From plugin
e2e-testing
159 skills2 hooks
Install
$ npx -y skills add burhankhatri/e2e-testing --skill tdd --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/tdd

Context preview

The summary Claude sees to decide when to auto-load this skill.

Enforces strict test-driven development. Use when implementing ANY feature, bugfix, or refactor — before writing implementation code. Also use when someone says 'add tests', 'write tests', 'test this', 'TDD', or when you're about to write production code of any kind. If you're

SKILL.md

tdd.SKILL.md
name: tdd
description: "Enforces strict test-driven development. Use when implementing ANY feature, bugfix, or refactor — before writing implementation code. Also use when someone says 'add tests', 'write tests', 'test this', 'TDD', or when you're about to write production code of any kind. If you're about to write code and there isn't a failing test for it yet, STOP and use this skill."

Test-Driven Development

The Iron Law

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

Write code before the test? Delete it. Start over.

**No exceptions:**

  • Don't keep it as "reference"
  • Don't "adapt" it while writing tests
  • Don't look at it
  • Delete means delete

Implement fresh from tests. Period.

Protecting the Tests

The agent optimizes for green. These rules make green mean true. Every one of them closes a loophole that was actually exploited in real projects using this skill (a July 2026 audit found 18 of 25 E2E tests in one repo permanently `test.skip`-guarded — they had never run once — and Anthropic, METR, and Kent Beck all document agents disabling or deleting tests to pass).

1. EVIDENCE THE RED      Run the new test and SHOW its failing output before
                         writing any implementation. It must fail for the
                         right reason — the missing behavior, not an import
                         error or typo. No observed red = the cycle never
                         happened.

2. FIX CODE, NOT TESTS   Never delete, weaken, .skip, or loosen a test to
                         reach green. If you believe the test itself is
                         wrong, STOP, say so explicitly, and get the user's
                         agreement before changing it.

3. SKIPPED = FAILING     "7 passed, 18 skipped" is a red suite. Always
                         report full counts (passed / failed / skipped).
                         A test that has never executed proves nothing.

4. CAN'T MAKE IT REAL?   Missing test user, credentials, test DB, seed
   STOP AND ASK.         data — that one-time setup is the user's call.
                         Ask for it. Do not mock around it. Do not skip.

Philosophy

**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.

**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.

**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.

See [tests.md](tests.md) for examples, [mocking.md](mocking.md) for mocking guidelines, and [deep-modules.md](deep-modules.md) / [interface-design.md](interface-design.md) for designing testable interfaces.

Anti-Pattern: Horizontal Slices

**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."

This produces **crap tests**:

  • Tests written in bulk test _imagined_ behavior, not _actual_ behavior
  • You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior
  • Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
  • You outrun your headlights, committing to test structure before understanding the implementation

**Correct approach**: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle.

WRONG (horizontal):
  RED:   test1, test2, test3, test4, test5
  GREEN: impl1, impl2, impl3, impl4, impl5

RIGHT (vertical):
  RED→GREEN: test1→impl1
  RED→GREEN: test2→impl2
  RED→GREEN: test3→impl3
  ...

Red-Green-Refactor Cycle

RED — Write Failing Test

Write ONE minimal test showing what should happen.

**Requirements:**

  • One behavior per test
  • Clear descriptive name ("and" in name? Split it)
  • Real code, no mocks unless unavoidable

<Good>

test('retries failed operations 3 times', async () => {
  let attempts = 0;
  const operation = () => {
    attempts++;
    if (attempts < 3) throw new Error('fail');
    return 'success';
  };
  const result = await retryOperation(operation);
  expect(result).toBe('success');
  expect(attempts).toBe(3);
});

Clear name, tests real behavior, one thing </Good>

<Bad>

test('retry works', async () => {
  const mock = jest.fn()
    .mockRejectedValueOnce(new Error())
    .mockResolvedValueOnce('success');
  await retryOperation(mock);
  expect(mock).toHaveBeenCalledTimes(2);
});

Vague name, tests mock not code </Bad>

Verify RED — Watch It Fail (MANDATORY, NEVER SKIP)

npm test path/to/test.test.ts

**Show the failing output in your message.** Then confirm:

  • Test fails (not errors)
  • Failure message is expected
  • Fails because feature is missing (not typos)

**Test passes?** You're testing existing behavior. Fix the test. **Test errors?** Fix the error, re-run until it fails correctly.

Best practice: commit the failing test on its own (`test: ...`) before implementing. The red phase becomes provable in git history.

GREEN — Minimal Code

Write the SIMPLEST code to pass the test. Nothing more.

Don't add features, refactor other code, or "improve" beyond what the test requires.

Verify GREEN — Watch It Pass (MANDATORY)

npm test path/to/test.test.ts

Confirm: Test passes, othe

Read more
Ships withe2e-testing

A set of 8 global skills for Claude Code that enforce disciplined, test-driven agentic development. Install once, use in any project.

Get the whole plugin

Other skills on e2e-testing.