agentleFS
ccpowerpackccpowerpack/claude-code-skills/skills/test-first/SKILL.md

test-first

Skill0 starsChanged 4 months agoMITView on GitHub

Enforced test-driven development — write the failing test, run it to prove it fails, then implement until green. Use when the user asks to build a feature "with TDD", "test-first", or wants high-confidence implementation of well-specified behavior.

24 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use refactor, code-review and 1 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • GitHubSkill

    refactor

    Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.

    Also in 4 projects that use this

  • GoogleSkill

    code-review

    Adversarial systems, concurrency, memory-model, and architecture code review protocol. Use this skill whenever asked to review code, PRs, diffs, or branches in Filament.

    Also in 4 projects that use this

  • SentrySkill

    skill-creator

    Create new agent skills following the Agent Skills specification. Use when asked to "create a skill", "add a new skill", "write a skill", "make a skill", "build a skill", or scaffold a new skill with SKILL.md. Guides through requirements, writing, registration, and verification.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

17 rules. 0 are common, 14 are distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 20 edited versions we read keep these.

  • 14

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Write ONE test for the next smallest behavior.

    line 16
  2. Distinctive:

    Run it. Paste the failure output.

    line 17
  3. Distinctive:

    If the test passes before you've implemented anything, the test is vacuous.

    line 19
  4. Distinctive:

    Write the minimum implementation that makes this test pass.

    line 23
  5. Distinctive:

    Run the test. Paste the passing output.

    line 24
  6. Distinctive:

    Run the full relevant suite — you may not proceed with other tests newly broken.

    line 25
  7. Distinctive:

    Run the suite again after refactoring.

    line 30
  8. Distinctive:

    Before starting, list the planned test cases as a checklist and keep it updated so the user sees progress.

    line 36
  9. Distinctive:

    Run ONE test fast; the tight loop dies if every red costs a full-suite run.

    line 53
  10. Distinctive:

    Cycle 1 — happy path: Test: first request with a key passes through to the handler.

    line 70
  11. Distinctive:

    Cycle 2 — the invariant: Test: second request with the same key returns the stored response and does NOT call the handler again.

    line 87
  12. Distinctive:

    Cycle 3 — boundary: Test: request without the header is never deduplicated.

    line 98
  13. Very specific:

    Wait for events, not durations: If the test must wait for async work, wait on a completion signal (future, channel, waitFor on an …

    line 114
  14. Distinctive:

    If the codebase has existing sync utilities or a clock abstraction, use those — matching house convention beats importing a new library.

    line 127
  15. Distinctive:

    No sleeping/polling-by-duration anywhere — see the table above.

    line 134
  16. Distinctive:

    Use real objects where cheap.

    line 135
  17. Very specific:

    Test names form a spec when listed: pytest --collect-only -q output should read like the feature's behavior list.

    line 137

“Common” is measured: kept by at least 6 in 10 of the 20 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Test First

You are implementing with strict TDD. The discipline exists for one reason: a test you never saw fail proves nothing. A green suite where every test was born green is indistinguishable from a suite of tautologies.

The loop

For each behavior, in order:

1. Red (gate: you must observe the failure)

  • Write ONE test for the next smallest behavior. Name it after the behavior, not the method: test_expired_token_is_rejected, not test_validate_2.
  • Run it. Paste the failure output.
  • Verify it fails for the RIGHT reason — an assertion failure on the behavior, not an import error, syntax error, or missing fixture. ModuleNotFoundError is not a red test; it's a broken test. Fix the scaffolding first and re-run until the failure is the assertion you wrote.
  • If the test passes before you've implemented anything, the test is vacuous. Rewrite it.

2. Green (gate: passing output for this test AND the suite)

  • Write the minimum implementation that makes this test pass. Resist implementing ahead of the tests — if you catch yourself writing a branch no test forces, delete it or write the test first.
  • Run the test. Paste the passing output.
  • Run the full relevant suite — you may not proceed with other tests newly broken.

3. Refactor

  • With green tests as a safety net: remove duplication, improve names, extract structure that has earned its existence.
  • Run the suite again after refactoring. Refactor step ends green or gets reverted.

Repeat until the feature's behaviors are covered.

Choosing the next test

Before starting, list the planned test cases as a checklist and keep it updated so the user sees progress. Order them to maximize learning per test:

Behavior classWhen to test itExample test name
Happy path, simplest inputFirst — forces the API shape into existencetest_first_request_passes_through
Core invariantSecond — the reason the feature existstest_duplicate_key_returns_cached_response
Boundary: empty / zero / absentAfter the invariant holdstest_request_without_key_is_not_deduplicated
Boundary: max / overflow / unicodeSame pass as other boundariestest_key_longer_than_255_chars_is_rejected
Error path: invalid inputAfter boundaries — needs the validation seamtest_malformed_key_returns_400_not_500
Error path: dependency failureLate — needs the fake/mock seam to existtest_store_outage_fails_open_not_closed
Concurrency / interactionLast — hardest to write, needs stable internalstest_concurrent_same_key_executes_handler_once
Time-dependent behaviorLast, with a fake clock (see below)test_key_expires_after_ttl

Skip a row only if it genuinely doesn't apply — say so in the checklist rather than silently omitting it. If mid-loop you discover a behavior the plan missed, add it to the checklist visibly before writing its test — the checklist is a living contract, not a prologue.

Prove-the-red commands per ecosystem

Run ONE test fast; the tight loop dies if every red costs a full-suite run.

EcosystemRun one testNotes
pytestpytest tests/test_idem.py::test_key_expires_after_ttl -x -q-k "expires" for substring match; -x stops at first failure
jestnpx jest idem.test.ts -t "expires after ttl"-t matches the test title; add --watch for the loop
vitestnpx vitest run idem.test.ts -t "expires after ttl"vitest without run starts watch mode — ideal for red-green
go testgo test ./middleware/ -run 'TestKeyExpiresAfterTTL$' -v -count=1-count=1 defeats the test cache — cached PASS is not evidence
cargocargo test key_expires_after_ttl -- --exact --nocapturesubstring match by default; --exact pins one test
JUnit (Maven)mvn test -Dtest='IdempotencyTest#keyExpiresAfterTtl' -qGradle: ./gradlew test --tests 'IdempotencyTest.keyExpiresAfterTtl'

Whatever the ecosystem: the red run and the green run must be the SAME command, pasted both times. If the runner has a test cache (go, gradle, turbo/nx-wrapped jest), disable it for the red/green pair — a cache hit reporting PASS is not an observation.

Worked example — idempotency-key middleware, three cycles

Feature: HTTP middleware that deduplicates POSTs carrying an Idempotency-Key header.

Cycle 1 — happy path. Test: first request with a key passes through to the handler.

$ pytest tests/test_idem.py::test_first_request_passes_through -x -q
E   AttributeError: module 'app.middleware' has no attribute 'IdempotencyMiddleware'

Wrong-reason failure — scaffolding, not behavior. Create the empty class, re-run:

E   assert handler.call_count == 1
E    +  where 0 = handler.call_count
1 failed in 0.04s

Right reason. Implement pass-through. 1 passed in 0.05s. Suite green.

Cycle 2 — the invariant. Test: second request with the same key returns the stored response and does NOT call the handler again.

$ pytest tests/test_idem.py::test_duplicate_key_returns_cached_response -x -q
E   assert handler.call_count == 1
E    +  where 2 = handler.call_count
1 failed in 0.05s

Implement: store (status, body) by key after the first call; short-circuit on hit. 2 passed in 0.06s.

Cycle 3 — boundary. Test: request without the header is never deduplicated.

E   assert handler.call_count == 2
E    +  where 1 = handler.call_count

Interesting red: the naive implementation cached under key None, so two keyless requests collided. The test caught a real bug before it shipped. Fix: bypass the store entirely when the header is absent. 3 passed in 0.07s.

Refactor. Extract ResponseStore from the middleware body — it has earned its existence: cycle 2 created it, cycle 3 gave it a second rule. Re-run: 3 passed in 0.07s.

Async and time-dependent code — no sleeps

sleep(0.5) in a test is a flake with a countdown timer. Two rules:

  1. Time is an injected dependency. The code under test reads a clock you control; the test advances it deterministically.
  2. Wait for events, not durations. If the test must wait for async work, wait on a completion signal (future, channel, waitFor on an observable condition) — never a fixed delay.
EcosystemFake clockAsync sync point
Pythonfreezegun.freeze_time() / time-machinepytest-asyncio: await the coroutine directly; drive the loop, don't sleep
JS/TS (jest)jest.useFakeTimers() + jest.advanceTimersByTime(60_000)await Promise.resolve() to flush microtasks; jest.runAllTimersAsync()
JS/TS (vitest)vi.useFakeTimers() + vi.advanceTimersByTime()await vi.runAllTimersAsync()
Goinject a Clock interface (jonboulle/clockwork, benbjohnson/clock)channels / sync.WaitGroup; t.Deadline()-bounded selects, never time.Sleep
Rust (tokio)#[tokio::test(start_paused = true)] + tokio::time::advance(Duration::from_secs(60)).awaitawait the task handle; paused time auto-advances past sleeps
JVMjava.time.Clock injected; Clock.fixed(...) in testsAwaitility await().until(...) polls a condition, not a duration

For the idempotency example: test_key_expires_after_ttl injects a fake clock, stores at T, advances the clock by ttl + 1s, asserts the handler runs again. Zero wall-clock waiting, zero flake surface.

If the codebase has existing sync utilities or a clock abstraction, use those — matching house convention beats importing a new library.

Where real time genuinely cannot be faked (integration test against a real broker, subprocess startup), poll a condition with a deadline — await().atMost(5, SECONDS).until(queueIsEmpty) — never a fixed delay. The deadline is a failure bound, not an expected duration: the test passes the instant the condition holds.

Test quality bar

  • Each test asserts one behavior; multiple asserts are fine if they describe one outcome.
  • No sleeping/polling-by-duration anywhere — see the table above.
  • Use real objects where cheap; mock only at process/network boundaries.
  • The test must read as documentation: given / when / then visible in its structure.
  • Test names form a spec when listed: pytest --collect-only -q output should read like the feature's behavior list.

Anti-patterns (hard no)

  • Writing the implementation first "to understand the problem", then backfilling tests.
  • Batch-writing 10 tests then implementing everything at once — that's test-after with extra steps.
  • Marking a test skipped/todo to get to green.
  • Weakening an assertion to make it pass. If the assertion was wrong, say so explicitly and justify the change.
  • Claiming red without pasted failure output, or pasting a red that failed on imports/fixtures rather than the assertion.
  • sleep() in any test, including "just 100ms to be safe".

Output contract

Finish with: the checklist of behaviors covered (each linked to its test name), final full-suite run output pasted, and any behavior you deliberately left untested (with reason).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.