Test-Driven Development skill for Codex
See it fail for the right reason, then pass. Codex stops writing code and matching tests in one pass. It agrees a test list with you, predicts each failure before running it, writes the least code to pass, and refactors only on a green suite. It avoids watch modes that hang the shell, keeps doubles at real boundaries, and finishes with a mutation spot-check that proves the tests can catch a broken line.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- test-driven-development Installs to ~/.agents/skills/test-driven-development/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install test-driven-development The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project test-driven-development Installs to .agents/skills/test-driven-development/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/test-driven-development.zipunzip -o test-driven-development.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) test-driven-development PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $test-driven-development, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds features and fixes test-first with a strict red-green-refactor loop - a written test list, one failing test at a time that fails for the stated reason, the smallest code that passes, then refactoring on green - using the repo's own runner (pytest, Vitest, Jest, go test, cargo test, JUnit). Use when the user asks for TDD or test-first work, when behaviour is specified by examples, when fixing a bug that needs a regression test, or when an agent keeps writing code and tests together that pass trivially.
Use it when
- The user asks for TDD or test-first development
- Behaviour is specified as examples or acceptance criteria
- A bug fix needs a regression test that provably fails first
- Agent-written tests keep passing trivially
Use something else when
- You need advice on structuring and naming tests in general: use unit-testing-patterns
- The code is old, untested and must not change behaviour yet: use legacy-code-characterization
How to check the result
Each cycle shows a red run with the predicted failure before the green run, the full suite passes, and deliberately breaking one new line makes at least one test fail.
The mistake it prevents
Editing the assertion to match what the code returns, which turns a failing test into a recording of the bug.
Every behaviour in the final diff was demanded by a test that was observed failing for the right reason before the code existed. The suite stays green between cycles, each cycle is small enough to explain in one sentence, and refactoring happens only on green.
Why this matters more for an agent
An agent that writes implementation and tests in one pass tends to produce tests that mirror the code, including its bugs. Seeing the test fail first is the only proof the test can detect anything. Treat the red step as mandatory evidence, not ceremony.
Setup
- Read
AGENTS.mdand the test config (pyproject.toml,vitest.config.*,jest.config.*,go.mod,Cargo.toml) to find the runner and the command for a single test. Match existing test file locations and naming. - Confirm the suite is green before starting. If it is red, report the existing failures and agree with the user whether to proceed; never start TDD on a red base.
- Pick the fastest focused command. You will run it dozens of times:
pytest tests/test_pricing.py -x -q -k "bulk" # -x stops at first failure
npx vitest run src/pricing.test.ts -t "bulk" # run mode; watch mode blocks the shell
npx jest src/pricing.test.ts -t "bulk"
go test ./pricing -run 'TestBulk' -count=1
cargo test pricing::bulk
./gradlew test --tests 'com.acme.PricingTest.bulk*'
Never use watch mode (vitest without run, jest --watch) inside Codex; it never
returns control.
The test list
Before any code, write the list of behaviours as one-line examples and show it to the user. It is the plan and the definition of done.
Bulk pricing
- [ ] 1 unit at list price -> 10.00
- [ ] 10 units get 5% off -> 95.00
- [ ] 100 units get 12% off -> 880.00
- [ ] discount tiers do not stack
- [ ] quantity 0 -> 0.00
- [ ] negative quantity -> ValueError
- [ ] result rounds half-even to cents
Order it: the simplest degenerate case first, then the main path, then edges and errors. New ideas that appear mid-cycle go on the list, not into the current cycle.
The cycle
Red
- Write exactly one test for the next item. Name it after the behaviour, not the method:
test_ten_units_get_five_percent_off. - Run it. It must fail, and fail for the reason you predicted. State the prediction before running (“fails with AssertionError 100.00 != 95.00”).
- Wrong failure reason (import error, typo, fixture missing) means the test is not yet a test. Fix the scaffolding until the failure is the intended assertion.
- A test that passes immediately either duplicates an earlier one or the behaviour already exists. Delete it or keep it as documentation, but say which.
Green
- Write the least code that makes this test pass. Hard-coding is allowed when it is truly the least code; the next test will force generalisation.
- Run the focused test, then the whole file or package. Everything green.
- Do not touch tests in this step. If you feel the urge to edit an assertion to make it pass, stop: either the test was wrong (say why to the user) or the code is.
Refactor
- Only on green. Remove duplication between code and tests, name things properly, extract helpers. Tests may be refactored too, one side at a time.
- Re-run after every change. A red during refactor means revert that change, not debug it.
- Commit at green when the repo workflow allows it, one cycle or a few related cycles per commit, so a bad step is cheap to undo.
Choosing the next test
| Situation | Next test |
|---|---|
| Implementation is hard-coded | A second example that breaks the hard-code (triangulation) |
| Logic feels obvious | Write it directly, then continue (obvious implementation) |
| Stuck for more than two attempts | A smaller test: a sub-step or a helper’s behaviour |
| Error handling | The specific exception type and message, not just “raises” |
| Many similar examples | Parametrize once the shape is stable, not before |
Test doubles
- Prefer real collaborators that are fast and deterministic (pure functions, in-memory repositories). Mock only at boundaries you do not own: network, clock, randomness, filesystem when slow.
- Inject time and randomness instead of patching globals: pass a
clockorrng. - Assert on outcomes (return value, state, emitted message), not on which internal methods were called. Interaction assertions belong only at real boundaries.
- In the Codex sandbox, network is often off. A test that needs the network is not a unit test; give it a fake or move it to integration tests.
Bug fixes, test-first
- Write a test that reproduces the reported bug through the public interface.
- Run it and see it fail with the reported symptom. If it passes, your understanding of the bug is wrong; go back to the reporter’s steps.
- Fix. Run the test, then the suite.
- Keep the test. Its name describes the behaviour, optionally with the issue number.
Hard rules
- No production code without a failing test that requires it, except pure refactors on green.
- Never weaken, skip (
@pytest.mark.skip,it.skip,t.Skip) or delete a failing test to get to green without the user’s agreement. - One failing test at a time. Two reds at once means you cannot tell which change fixed what.
- Do not assert on private internals to drive design; drive through the public API.
- Do not mass-generate the test list as code up front. The list is prose; tests arrive one per cycle.
When TDD is the wrong tool
Spikes to learn an unknown API, UI layout tweaks, and throwaway scripts. Say so, spike without tests, then delete the spike and rebuild test-first if it becomes real code.
Verification
- Every item on the test list is checked off or explicitly deferred with a reason.
- Full suite green using the command from
AGENTS.md. - Mutation spot-check: break one line of the new logic (flip a comparison, change a constant), run the tests, confirm at least one fails, revert. For a systematic version see mutation-testing.
- No skipped or focused tests left behind:
rg -n "\.only\(|\.skip\(|@pytest\.mark\.skip|t\.Skip\(" <test dirs>.
Report
### TDD summary: <feature>
Cycles: 7 (all observed red before green)
Test list: 7/7 done; deferred: <none | item + reason>
Files: src/pricing.py (+42), tests/test_pricing.py (+68)
Design notes: tier lookup extracted to `tier_for(quantity)` during refactor of cycle 4
Mutation spot-check: changing `>=` to `>` in tier_for fails 2 tests
Suite: `pytest -q` 214 passed What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Test-Driven Development"
short_description: "See it fail for the right reason, then pass"
default_prompt: "$test-driven-development Implement the bulk pricing rules test-first, one test at a time" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.