원클릭으로
test-driven-development
Use when implementing any feature, bug fix, or behavior change — before writing production code
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when implementing any feature, bug fix, or behavior change — before writing production code
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
[Workflow] Explore an idea and design a solution before implementing
Use when the what or how of a change has open questions requiring collaborative design dialogue with the user — before committing to an approach
[Agent] Subagent persona for reviewing code changes against a plan and quality standards
Use when approaching complex exploration, research, or synthesis tasks — understanding systems, codebases, domains, or architectures before proposing changes or designs
Use when facing 2+ tasks that can proceed without shared state, sequential dependencies, or mutual file edits
[Workflow] Execute an implementation plan task by task with review checkpoints
| name | test-driven-development |
| description | Use when implementing any feature, bug fix, or behavior change — before writing production code |
| metadata | {"owner":"shrug-labs","last_updated":"2026-03-23T00:00:00.000Z"} |
Write the test first. Watch it fail. Write minimal code to pass.
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over. Implement fresh from tests.
No exceptions:
Violating the letter of this rule IS violating the spirit.
If you're unsure whether TDD applies, it applies.
digraph tdd {
rankdir=LR;
red [label="RED\nWrite failing test" shape=box style=filled fillcolor="#ffcccc"];
verify_red [label="Fails correctly?" shape=diamond];
green [label="GREEN\nMinimal code" shape=box style=filled fillcolor="#ccffcc"];
verify_green [label="All tests pass?" shape=diamond];
refactor [label="REFACTOR\nClean up" shape=box style=filled fillcolor="#ccccff"];
done [label="Next cycle" shape=doublecircle];
red -> verify_red;
verify_red -> green [label="yes — expected failure"];
verify_red -> red [label="no — wrong failure or error"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no — fix code, not test"];
refactor -> verify_green [label="must stay green"];
verify_green -> done [label="clean"];
done -> red;
}
Write a single test that describes the behavior you want. One assertion, one behavior.
Requirements:
test_rejects_empty_email, not test1Run the test. This step is mandatory, never skip it.
| Language | Command |
|---|---|
| Go | go test ./path/to/package/ -run TestName |
| Python | pytest path/to/test_file.py::test_name -v |
| Node/TS | npm test -- --testPathPattern=path/to/test |
| Shell | bats path/to/test.bats or run the test script directly |
| Rust | cargo test test_name |
| Other | Use the project's existing test runner. Check Makefile, package.json, or CI config. |
Stub new functions first. If the test references a function that doesn't exist yet, stub it before running (return zero values, panic("not implemented"), or raise NotImplementedError). A compilation error is not valid RED — you haven't exercised the assertion.
Confirm all three:
Test passes immediately? You are testing existing behavior. Fix the test.
Write the simplest code that makes the test pass. Nothing more.
Run the full test suite for the affected package/module, not just the new test.
Confirm:
New test fails? Fix the production code. Do not weaken the test.
Existing test broke? Fix it now, before proceeding.
Only after GREEN is verified:
Run tests after every refactor step. Tests must stay green. Do not add new behavior during refactor.
Return to RED for the next behavior.
digraph runner {
start [label="What language?" shape=diamond];
go [label="go test ./..." shape=box];
py [label="pytest" shape=box];
node [label="npm test / jest / vitest" shape=box];
rust [label="cargo test" shape=box];
shell [label="bats / shunit2" shape=box];
other [label="Check Makefile\nor CI config" shape=box];
run [label="Run it" shape=doublecircle];
start -> go [label="Go"];
start -> py [label="Python"];
start -> node [label="JS/TS"];
start -> rust [label="Rust"];
start -> shell [label="Bash/Shell"];
start -> other [label="Other"];
go -> run;
py -> run;
node -> run;
rust -> run;
shell -> run;
other -> run;
}
Always check for a project-specific test command first (Makefile test target, scripts.test in package.json, CI config). Use it over generic commands.
Asserting that a mock was called, or that a mock element exists, tells you nothing about real behavior. If you must mock (external API, database, network), assert on the outcome of your code, not on the mock itself.
Gate: Before any assertion involving a mock — ask: "Am I testing my code's behavior, or the mock's existence?" If the latter, delete the assertion.
Methods that exist only for test cleanup, test inspection, or test setup pollute the production API. Put test utilities in test files or a test helper package.
Gate: Before adding a method to production code — ask: "Is this called outside of tests?" If no, move it to test utilities.
Asserting that len([]int{1, 2}) == 2 tests the language, not your code. A RED test must exercise your function's contract boundary — its interaction with real inputs, edge cases, or state. If removing your function wouldn't change the test outcome, the test is worthless.
Gate: Before declaring RED — ask: "Does this test fail because MY code's behavior is wrong, or because a language builtin would need to misbehave?" If the latter, rewrite to test the actual contract.
Mocking "to be safe" or "because it might be slow" breaks tests that depend on side effects of the mocked code. Understand what the real method does before replacing it.
Gate: Before mocking any method:
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing — you never saw them catch the bug. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" Different question, different quality. |
| "Already manually tested" | Ad-hoc, no record, cannot re-run. Manual does not equal systematic. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is the real waste. |
| "Keep as reference, write tests first" | You will adapt it. That is tests-after in disguise. Delete means delete. |
| "Need to explore first" | Fine. Throw away the exploration. Then start with TDD. |
| "Hard to test = unclear requirements" | Hard to test = hard to use. Listen to the test. Simplify the interface. |
| "TDD will slow me down" | TDD is faster than debugging. Measure wall-clock time including debug cycles. |
| "This is different because..." | No, it is not. |
All of these mean: delete the code, start over with RED.
| Situation | Approach |
|---|---|
| New feature | Full TDD cycle — RED, GREEN, REFACTOR for each behavior |
| Bug fix | Write a test that reproduces the bug (RED), fix it (GREEN), refactor |
| Refactoring existing code | Ensure tests exist first. If not, add characterization tests, then refactor under green. |
| Legacy code with no tests | Add tests for the specific behavior you are changing. Do not boil the ocean. |
| Spike / exploration | Write throwaway code. Delete it. Start over with TDD. The spike informed your understanding, not your implementation. |
| Performance optimization | Write a benchmark test that captures current behavior. Optimize under green. |
Before declaring implementation complete:
Cannot check all boxes? You skipped TDD. Start over.
Once the implementation is complete and all tests pass:
Do not skip the final verification gate. "Tests pass" is necessary but not sufficient — the verification-before-completion rule confirms the change is actually done.