Preface

The most common scenario when asking a programming Agent to write tests is not that it refuses to write them, but that it moves too fast and writes too many at once. If you say “add a checkout feature and add tests along the way”, it will often lay out the entire test skeleton first before filling in the implementation all at once. On the surface, both red and green test states work, but in reality, these tests validate the interface shape it imagined, not the behavior users can actually observe. A single internal refactor will break the tests; if assertions use the same calculations as the implementation, the tests will never fail.

Matt Pocock packaged this into an Agent Skill named tdd. It does not handle scheduling, ticket splitting, or submissions for you. It only defines how each iteration of the red-green cycle should proceed: what to test, where to test it, writing exactly one test at a time, and writing just enough implementation to make that test pass at a time. The repo mattpocock/skills calls this kind of practice “Skills for real engineers”, and tdd is one of the engineering skills marked as model-invoked: you can input /tdd, and the Agent will independently reference it when tasked with writing tests first, red-green-refactor, or integration tests.

This article cross-references the official SKILL.md, supporting tests.md / mocking.md, and the author’s skill explanation on aihero.dev to introduce what this Skill solves, how to install it, and how to use it.

What it is

tdd is a TDD规程 (test-driven development protocol) for Agents, not a testing framework or a workflow that builds features all at once. Its official positioning is very clear: it is a reference (rulebook), not a driver (execution program). You, or the implement Skill in the same repository, are the ones who actually run the cycle.

Source and attribution:
- Author: Matt Pocock (Total TypeScript / AI Hero)
- Repository: https://github.com/mattpocock/skills
- Directory: skills/engineering/tdd/
- License: MIT
- Supporting documentation: https://www.aihero.dev/skills-tdd
- Distribution page: https://skills.sh/mattpocock/skills/tdd

The frontmatter of SKILL.md lists the trigger conditions: the user wants to build features or fix bugs by writing tests first, mentions red-green-refactor, or needs to write integration tests. The repo’s README sums it up in one sentence: practice test-driven development by vertical slicing, handling one behavior at a time.

The problem it solves is specific: by default, Agents take a “horizontal slicing” approach—writing all tests first before writing all implementation. tdd requires switching to “vertical slicing”: one test → a minimal just-enough implementation → then write the next test. Each iteration is a tracer bullet, using what you learned in the previous cycle to decide what to test next.

Core Rules

The Skill text requires that every section below be referenced during every red-green cycle, not revisited after completion.

What counts as a good test

Tests must validate behavior through the public interface, not internal implementation details. The implementation can be completely rewritten without breaking the tests. Good tests read like specifications, for example "user can checkout with valid cart", letting you immediately see what capabilities the system has.

The official tests.md gives a good example that follows the real call path:

// GOOD: Tests observable behavior
test("user can checkout with valid cart", async () => {
  const cart = createCart();
  cart.add(product);
  const result = await checkout(cart, paymentMethod);
  expect(result.status).toBe("confirmed");
});

Compare this to the bad example below, which tests internal collaboration patterns. The test will fail if you refactor without changing the actual behavior:

// BAD: Tests implementation details
test("checkout calls paymentService.process", async () => {
  const mockPayment = jest.mock(paymentService);
  await checkout(cart, payment);
  expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
});

The Skill also emphasizes that expected values must come from an independent source of truth—literal values from specifications, manually calculated examples, or requirements themselves. You should not reuse the implementation’s own algorithm to calculate the expected value. The Skill calls out this tautological test:

// BAD: Expected value recalculated using the code's own logic, test is meaningless
test("calculateTotal sums line items", () => {
  const items = [{ price: 10 }, { price: 5 }];
  const expected = items.reduce((sum, i) => sum + i.price, 0);
  expect(calculateTotal(items)).toBe(expected);
});

// GOOD: Expected value is an independent known literal
test("calculateTotal sums line items", () => {
  expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
});

Additionally, do not bypass the interface to check a database to prove “write succeeded”. Instead, read the data back using the same public interface:

// GOOD: Validate via the public interface
test("createUser makes user retrievable", async () => {
  const user = await createUser({ name: "Alice" });
  const retrieved = await getUser(user.id);
  expect(retrieved.name).toBe("Alice");
});

The characteristics of a good test can be summarized as: tests behaviors that callers care about, only uses public APIs, remains green after internal refactoring, describes WHAT rather than HOW, and has exactly one logical assertion per test.

Test at the seams

The Skill borrows the term seam from Michael Feathers: tests should be placed at public boundaries, where you observe behavior, rather than reaching inside modules. Tests should only be written at pre-agreed seams. Before writing any test, the Agent must first list the seams it plans to test and confirm them with you; you must approve the seams before any tests are written.

It will ask: “What is the public interface, and which seams should we test at?”

You cannot test every edge case. Pre-agreeing on seams lets you focus testing effort on critical paths and complex logic. How deep the interface should be, where to place seams, and what to expose publicly are terms defined in the codebase-design Skill from the same repository. The official documentation makes it clear that tdd removed its own in-depth module notes in v1.0, instead referencing this shared vocabulary. codebase-design needs to be installed alongside, but it is a reference for lookup, not a separate design session.

When exploring the codebase, if there is a CONTEXT.md file in the repository, use the domain terminology from it when writing test names and interface names, and follow any relevant ADRs.

Three anti-patterns

SKILL.md calls out three types of tests that are not worth keeping:
1. Implementation-coupled: Mocking internal collaborators, testing private methods, or using bypass validation (directly checking the database instead of going through the interface). The litmus test: you refactor without changing behavior, but the test turns red.
2. Tautological: Assertions use the code’s own logic to calculate expected values, such as expect(add(a, b)).toBe(a + b), or manually generated snapshots derived from the implementation. These tests will never conflict with the code.
3. Horizontal slicing: Writing all tests first before writing all implementation. Batch tests validate imagined behavior, testing “what things look like” rather than what users can do, and lock in test structure before you even understand the implementation.

The correct approach is vertical slicing: one test → one implementation → repeat. Each test is a tracer bullet, using what you learned from the previous cycle to write the next one. The comparison can be written as:

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

Cycle rules

The current cycle in SKILL.md only has two steps:
- Red before green. Write a failing test first, then write just enough code to make it pass. Do not prepare for future tests in advance, and do not add speculative features.
- One slice at a time. Each iteration: one seam, one test, one minimal implementation.
- Refactoring is not part of this cycle. Refactoring falls under the code-review Skill in the same repository, to be done during the review phase, not included in the red-green implementation cycle.

There is one important clarification here. The repository README, skills.sh summary, and the description field in SKILL.md still mention red-green-refactor. The author explained in the aihero.dev skill documentation: the refactoring step was removed in June 2026, because Agents almost never perform this step properly, and splitting implementation and review into two sessions is more appropriate; the description field was not updated to match, corresponding to repository issue #589. So even if you say “red-green-refactor”, this Skill will still be triggered, and it will only run red → green, with refactoring handled by code-review.

When to mock

The supporting document mocking.md specifies: only mock at system boundaries.

You can mock:
- External APIs (payment, email, etc.)
- Databases (sometimes acceptable, testing libraries are recommended instead)
- Time / random number generators
- File system (sometimes acceptable)

You should not mock: your own classes and modules, internal collaborators, anything you control.

The official gives two design recommendations to make boundaries mockable.

First, use dependency injection instead of instantiating external clients inside functions:

// Easy to mock
function processPayment(order, paymentClient) {
  return paymentClient.charge(order.total);
}

// Hard to mock
function processPayment(order) {
  const client = new StripeClient(process.env.STRIPE_KEY);
  return client.charge(order.total);
}

Second, create separate functions for each external operation, instead of a single generic fetch with many branches:

// GOOD: Each function can be mocked individually
const api = {
  getUser: (id) => fetch(`/users/${id}`),
  getOrders: (userId) => fetch(`/users/${userId}/orders`),
  createOrder: (data) => fetch("/orders", { method: "POST", body: data }),
};

// BAD: Need to add internal conditions when mocking
const api = {
  fetch: (endpoint, options) => fetch(endpoint, options),
};

Installation and Enablement

tdd follows the standard SKILL.md format, and works with Agent Skill-supporting tools like Cursor, Codex CLI, and Claude Code. The official provides two installation paths, which are mutually exclusive per the README; installing both will result in duplicate Skills.

The repository directory structure is roughly:

skills/engineering/tdd/
  SKILL.md
  tests.md
  mocking.md
  agents/openai.yaml

tests.md and mocking.md are reference materials to consult during the cycle, not executable scripts.

1. Install only tdd (and its dependency codebase-design)

The installation command for this Skill on skills.sh is:

npx skills add https://github.com/mattpocock/skills --skill tdd

The author wrote on aihero.dev: after v1.0, tdd depends on codebase-design for the seam / deep module vocabulary, so it needs to be installed alongside. tdd itself is stateless and will not write files to the repository.

2. Install the full suite via skills.sh, then select as needed

This is the default method for Codex and other Agents per the README, and also works with Cursor. The installer will let you choose Skills and target Agents, and write files into the repository, which you can modify later:

npx skills@latest add mattpocock/skills

Include tdd when selecting the Skills. If you want to use the full set of engineering skills (ticket splitting, implementation, review), the README requires also selecting setup-matt-pocock-skills. After installation, run /setup-matt-pocock-skills in the Agent to configure the issue tracker, triage labels, and documentation storage location. You do not need to go through this full repository setup if you only want to use tdd for the red-green cycle alone.

Update files already copied locally:

npx skills update

3. Claude Code plugin (full read-only package, follows upstream updates)

The current README states: this Skill suite is now available in the Claude Code official marketplace, so you do not need to add the source first.

claude plugins install mattpocock-skills

You can also run this in a Claude Code session:

/plugin install mattpocock-skills

The plugin installs a full read-only package that updates automatically when the author publishes new versions. Do not mix this installation method with the skills.sh route.

How Cursor discovers it

Cursor automatically loads Skills from these directories:
| Location | Scope |
| — | — |
| .agents/skills/, .cursor/skills/ | Current project |
| ~/.agents/skills/, ~/.cursor/skills/ | Current user global |
| .claude/skills/, .codex/skills/ and their corresponding home directories | Compatible with Claude Code / Codex |

Each Skill is a folder containing SKILL.md. npx skills will write files to the corresponding directory based on the Agent you select. You can also manually copy the official directory to your project, for example .cursor/skills/tdd/SKILL.md. Type / in the Cursor Agent chat box and search for tdd to manually invoke the Skill.

Typical Usage

Invoke directly

If you have a clearly defined behavior with clear input and observable output, just run /tdd. You can also write prompts like “write failing tests first then implement”, “do this using TDD”, or “need integration tests” in the conversation, and the Agent will select the Skill automatically based on its description.

The official expected workflow you will see is:
1. The Agent first lists the public seams it plans to test, and pauses waiting for your confirmation. No test files will be written until you confirm.
2. Write one failing test, and confirm that it is red because the behavior does not yet exist, not because the test itself has errors.
3. Write only enough implementation to make this single test pass.
4. Write the next test. Do not submit a batch of tests all at once.

The first test is the tracer bullet: first prove that one end-to-end path works, then expand outward.

Integrate into a full engineering pipeline

In the same repository, tdd acts as an engine inside the build step, not a standalone “do everything” step. The official main pipeline is written as:

grill-with-docs → to-spec → to-tickets → implement → code-review

The meaning is: to-spec first agrees on the test seams; implement drives tdd based on tickets; code-review checks that tests were only written at agreed-upon seams, and handles the refactoring that tdd no longer includes. If you already have a spec or tickets and want to run through the build once, the official recommends running /implement instead of using /tdd alone as a full workflow.

If you do not have a full spec and just want to test-first write a specific behavior, run /tdd directly.

How to tell it’s working correctly

The official acceptance signals include:
- It pauses to report the seams and wait before any test files are created.
- Only one test appears at a time, first red then green, before writing the next one; not a batch of tests paired with a batch of code.
- Test names read like capabilities (user can checkout with valid cart), not internal steps (checkout calls paymentService.process).
- Expected values in assertions can be traced back to specifications or known examples, not recalculated using the implementation.
- Renaming an internal function should not break the test suite.
- Mocks only appear at external boundaries (payment APIs, clocks), not your own internal modules.

适用场景与注意事项

Suitable scenarios for tdd: behaviors that are already pinned down, with clear input and observable output. Official examples include business logic, request/response contracts, data transformation, and validation.

Scenarios that are not suitable, or where the Skill itself has identified gaps: configuration, wiring, glue code, pure type annotations, directly delegating CRUD operations to lower layers. Such changes often lack an independent source of truth for assertions, and forcing a TDD cycle will easily lead to the tautological tests the Skill warns against. This corresponds to repository issue #746, and the documentation states that until this issue is closed, whether to use TDD for such changes is up to you or the repository’s CLAUDE.md file.

There are several other documented limitations worth knowing in advance:
1. The Agent may still write implementation first. The Skill documentation notes: when asked why it did not write tests first, some models reply “I read the rules but fell back into my usual habits”. The Skill does not enforce 100% compliance. If you need to strictly enforce red before green, you need to monitor the run, rather than assuming the presence of the file guarantees discipline.
2. Do not default to starting with browser/E2E tests. Some users have seen the Agent first write Playwright tests, then go through a long cycle before concluding the tests are broken—when the feature did not even exist yet. Browser tests are too slow, and the red-green feedback loop becomes inefficient. The official recommends specifying in CLAUDE.md: stabilize behaviors in faster tests first, then add browser tests later.
3. Selecting seams can get stuck. This is the most commonly reported friction point (issue #6