Skip to content
Development
Skill

/test-driven-development

Red-green-refactor development methodology requiring verified test coverage. Use for feature implementation, bugfixes, refactoring, or any behavior changes where tests must prove correctness.

From plugin
agent-skills-library
414 skills

Claude Code

$ npx -y skills add izyanrajwani/agent-skills-library --skill test-driven-development --agent claude-code

SKILL.md

test-driven-development.SKILL.md
name: test-driven-development
description: Red-green-refactor development methodology requiring verified test coverage. Use for feature implementation, bugfixes, refactoring, or any behavior changes where tests must prove correctness.

Test-Driven Development

Write test first. Watch it fail. Write minimal code to pass. Refactor.

**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.

The Iron Law

NO BEHAVIOR-CHANGING PRODUCTION CODE WITHOUT A FAILING TEST FIRST

Wrote code before test? Delete it completely. Implement fresh from tests.

**Refactoring is exempt:** The refactor step changes structure, not behavior. Tests stay green throughout. No new failing test required.

Red-Green-Refactor Cycle

RED ──► Verify Fail ──► GREEN ──► Verify Pass ──► REFACTOR ──► Verify Pass ──► Next RED
         β”‚                         β”‚                            β”‚
         β–Ό                         β–Ό                            β–Ό
      Wrong failure?           Still failing?              Broke tests?
      Fix test, retry          Fix code, retry             Fix, retry

RED - Write Failing Test

Write one minimal test for one behavior.

**Good example:**

test('retries failed operations 3 times', async () => {
  let attempts = 0;
  const operation = async () => {
    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, asserts observable outcome*

**Bad example:**

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

*Vague name, asserts only call count without verifying outcome, tests mock mechanics not behavior*

**Requirements:** One behavior. Clear name. Real code (mocks only if unavoidable).

Verify RED - Watch It Fail

**MANDATORY. Never skip.**

npm test path/to/test.test.ts

Test must go red for the right reason. Acceptable RED states:

  • Assertion failure (expected behavior missing)
  • Compile/type error (function doesn't exist yet)

Not acceptable: Runtime setup errors, import failures, environment issues.

Test passes immediately? You're testing existing behaviorβ€”fix test. Test errors for wrong reason? Fix error, re-run until it fails correctly.

GREEN - Minimal Code

Write simplest code to pass the test.

**Good example:**

async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
  for (let i = 0; i < 3; i++) {
    try {
      return await fn();
    } catch (e) {
      if (i === 2) throw e;
    }
  }
  throw new Error('unreachable');
}

*Just enough to pass*

**Bad example:**

async function retryOperation<T>(
  fn: () => Promise<T>,
  options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; }
): Promise<T> { /* YAGNI */ }

*Over-engineered beyond test requirements*

Write only what the test demands. No extra features, no "improvements."

Verify GREEN - Watch It Pass

**MANDATORY.**

npm test path/to/test.test.ts

Confirm: Test passes. All other tests still pass. Output pristine (no errors, warnings).

Test fails? Fix code, not test. Other tests fail? Fix now before continuing.

REFACTOR - Clean Up

After green only: Remove duplication. Improve names. Extract helpers.

Keep tests green throughout. Add no new behavior.

Repeat

Next failing test for next behavior.

Good Tests

**Minimal:** One thing per test. "and" in name? Split it. ❌ `test('validates email and domain and whitespace')`

**Clear:** Name describes behavior. ❌ `test('test1')`

**Shows intent:** Demonstrates desired API usage, not implementation details.

Example: Bug Fix

**Bug:** Empty email accepted

**RED:**

test('rejects empty email', async () => {
  const result = await submitForm({ email: '' });
  expect(result.error).toBe('Email required');
});

**Verify RED:**

$ npm test
FAIL: expected 'Email required', got undefined

**GREEN:**

function submitForm(data: FormData) {
  if (!data.email?.trim()) {
    return { error: 'Email required' };
  }
  // ...
}

**Verify GREEN:**

$ npm test
PASS

**REFACTOR:** Extract validation helper if pattern repeats.

Red Flags - STOP and Start Over

Any of these means delete code and restart with TDD:

  • Code written before test
  • Test passes immediately (testing existing behavior)
  • Can't explain why test failed
  • Rationalizing "just this once" or "this is different"
  • Keeping code "as reference" while writing tests
  • Claiming "tests after achieve the same purpose"

When Stuck

| Problem | Solution | |---------|----------| | Don't know how to test | Write the API you wish existed. Write assertion first. | | Test too complicated | Design too complicated. Simplify the interface. | | Must mock everything | Code too coupled. Introduce dependency injection. | | Test setup huge | Extract helpers. Still complex? Simplify design. |

Legacy Code (No Existing Tests)

The Iron Law ("delete and restart") applies to **new code you wrote without tests**. For inherited code with no tests, use characterization tests:

1. Write tests that capture current behavior (even if "wrong") 2. Run tests, observe actual outputs 3. Update assertions to match reality (these are "golden masters") 4. Now you have a safety net for refactoring 5. Apply TDD for new behavior changes

Characterization tests lock down existing behavior so you can refactor safely. They're the on-ramp, not a permanent state.

Flakiness Rules

Tests must be deterministic. Ban these in unit tests:

  • **Real sleeps / delays** β†’ Use fake timers (`vi.useFakeTimers()`, `jest.useFakeTimers()`
Read more
@izyanrajwani
Ships withagent-skills-library

πŸ› οΈ Enhance programming workflows with a curated library of agent skills for tasks like code review, debugging, and planning for efficient project execution.

Stars on GitHub

Momentum

stars in total
4
Star history of izyanrajwani/agent-skills-library on GitHub, a chart by star-history.com

Other skills on agent-skills-library.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android β€” semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about test-driven-development.

Which coding agents can use test-driven-development?
test-driven-development installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.