t-wada TDD — TypeScript × Vitest
Practice strict Test-Driven Development in the style of t-wada / Kent Beck. The goal is 動作するきれいなコード — clean code that works, grown one small verified step at a time.
No production code without a failing test you have seen fail. No exceptions.
Project environment (auto-detected)
- Package manager: !
if [ -f pnpm-lock.yaml ]; then echo pnpm; elif [ -f yarn.lock ]; then echo yarn; elif [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun; elif [ -f package-lock.json ]; then echo npm; else echo "npm (no lockfile found)"; fi
- Vitest: !
node -p "const p = require('./package.json'); const v = { ...p.dependencies, ...p.devDependencies }.vitest; v ? v : 'NOT found in package.json'" 2>/dev/null || echo "package.json not found"
- Config: !
found=$(ls vitest.config.* vitest.workspace.* vite.config.* 2>/dev/null); echo "${found:-none found — check package.json or ask the user}"
- Globals: !
grep -qsE "globals[\"']? *: *true" vitest.config.* vite.config.* 2>/dev/null && echo "enabled — use describe/it/expect/vi without imports" || echo "not detected — import { describe, it, expect, vi } from 'vitest'"
- Test scripts: !
node -p "Object.entries(require('./package.json').scripts ?? {}).filter(([k]) => k.includes('test')).map(([k, v]) => k + ': ' + v).join(' | ') || 'none'" 2>/dev/null || echo "package.json not found"
If anything above is missing or contradicts the repository, read package.json and the Vitest config yourself before running tests. Never guess commands.
Applicability gate (decide first)
| Task |
TDD required? |
| New feature / new behavior |
Yes |
| Bug fix |
Yes — failing regression test first |
| Refactoring logic |
Yes — green tests before, during, after |
| Changing untested legacy code |
Yes — characterization tests first (see below) |
| Comments, docs, formatting only |
No |
| Pure type declarations with no runtime effect |
No — consider type tests (expectTypeOf) |
| Config / CI / dependency bumps |
No — but run the full suite afterwards |
If the user explicitly opts out of TDD, note the tradeoff in one sentence and follow their instruction. Otherwise this skill is binding.
Iron rules
- Test first, always. Production code may only be written in response to a failing test you have watched fail.
- Never start watch mode. Bare
vitest (or a test script without run) can block the session forever. Always use vitest run ….
- Verify Red before Green. Run the new test and read the output. It must fail for the expected reason — an assertion failure describing the missing behavior, not a typo, broken import, or setup error.
- Minimal Green. Write the least code that passes. Hard-coding is allowed (仮実装); generality is earned through more tests, not anticipated.
- Refactor only on green. Never restructure while any test is red.
- Never weaken a test to pass. No deleting, no loosening assertions, no
.skip without a written reason. If a test is wrong, fix it and say why.
- One behavior per test. Name the test after the behavior —
it('returns 0 for an empty cart') — never after the implementation.
- Run tests after every Green and every Refactor step. Affected tests during the cycle, the full suite before declaring done.
- Leave no
.only. Search and remove before finishing (CI rejects it via --allowOnly=false).
- Tests are first-class code. Same naming and readability standards — but DAMP over DRY: duplication in tests is fine when it makes intent clearer.
The cycle
探索 Explore → Plan (test list) → 🔴 Red → 🟢 Green → 🔵 Refactor → next item … → Done
探索 Explore
Read the relevant code, types, and existing tests before planning. If an unfamiliar API needs investigation, a throwaway spike is allowed — but spike code must be deleted or rebuilt test-first before integration. Never promote untested spike code to production.
Plan — write the test list
- Decompose the task into small, independently verifiable behaviors.
- Order them simplest and most fundamental first. Include edge cases: empty, zero, negative, boundaries, error paths.
- Record the list as
it.todo(…) placeholders in the test file and mirror it in your task tracker.
- The list is alive: append discoveries as you go; never silently drop an item.
describe('calculateTotal', () => {
it.todo('returns 0 for an empty cart');
it.todo('sums item prices');
it.todo('applies percentage discount');
it.todo('never returns a negative total');
});
🔴 Red — one failing test
- Pick one item from the list.
- Write the test assert-first: write the
expect line, then work backwards to the setup. Expected values are concrete literals — never computed with the logic you are about to implement.
- If the code under test does not exist yet, add only a minimal skeleton (exported stub with the right signature) so the failure is an assertion failure, not a compile error.
- Run only that test:
npx vitest run src/cart.test.ts
npx vitest run -t "applies percentage discount"
- Gate: read the failure output and confirm it fails for the expected reason. If the test passes immediately, stop — either the behavior already exists or the test asserts nothing. Investigate before proceeding.
🟢 Green — minimal pass
Pick the smallest workable strategy:
| Strategy |
When |
How |
| 仮実装 (Fake It) |
Unsure how to implement |
Return a hard-coded constant; generalize later |
| 三角測量 (Triangulation) |
The fake must become real |
Add a second test with different values the constant cannot satisfy, then generalize |
| 明白な実装 (Obvious Implementation) |
Implementation is trivial and certain |
Just write it — if it does not go green immediately, fall back to smaller steps |
Run the affected tests. Gate: the new test passes and nothing else broke. Speed to green beats elegance — sins committed here are repaid in Refactor.
🔵 Refactor — clean up on green
- Remove duplication — including duplication between test and production code (the same
90 appearing in both is the signal to generalize a fake).
- Improve names, extract functions, simplify structure. Refactor test code too.
- After each small step, rerun affected tests. Green → continue. Red → revert that step immediately; do not debug forward.
- Tidy First: when committing, keep structural changes (rename / move / extract) in separate commits from behavioral changes. Commit only on green.
Then return to the list and pick the next item.
Done — definition of done
When stuck
- No green after ~3 attempts → the step is too big. Revert to the last green state, split the behavior smaller, retry.
- Refactor broke a test → revert the refactoring and take a smaller step. Never debug forward on red.
- Unrelated tests started failing → stop; treat that as the current Red. No shotgun fixes.
- Flaky test → quarantine with
it.skip + a reason comment + a list item, and report it. Never delete it.
- Genuinely blocked → report the test-list state honestly instead of forcing green.
Progress reporting
Report each cycle compactly so the user can follow along:
🔴 applies percentage discount — fails as expected (expected 90, received 100)
🟢 pass (7 tests, 112ms) — fake-it with constant
🔵 generalized via triangulation; extracted applyDiscount() — all green
Untested legacy code
Before changing behavior in code that has no tests: pin the current behavior with characterization tests — assert what the code actually does today, even if it looks wrong, and report oddities to the user instead of silently "fixing" them. Once green, proceed with the normal cycle.
References (load on demand)
- vitest.md — agent-safe CLI usage, modifiers, matchers, async patterns, type tests, snapshots, coverage
- test-style.md — readable test patterns: AAA, naming,
it.each, fixtures, assertion style
- mocking.md — test-double policy and
vi.* usage: mock boundaries, not your own logic
- anti-patterns.md — catalog of TDD violations with corrections
1---2name: tdd3description: Strict t-wada style Red-Green-Refactor TDD for TypeScript + Vitest projects. Use when implementing features, fixing bugs, changing runtime behavior, or refactoring logic — any task that adds or modifies production code. Enforces test-first development with a verified Red, minimal Green, and disciplined Refactor, plus agent-safe Vitest execution rules.4---56# t-wada TDD — TypeScript × Vitest78Practice strict Test-Driven Development in the style of t-wada / Kent Beck. The goal is **動作するきれいなコード — clean code that works**, grown one small verified step at a time.910**No production code without a failing test you have seen fail. No exceptions.**1112## Project environment (auto-detected)1314- Package manager: !`if [ -f pnpm-lock.yaml ]; then echo pnpm; elif [ -f yarn.lock ]; then echo yarn; elif [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun; elif [ -f package-lock.json ]; then echo npm; else echo "npm (no lockfile found)"; fi`15- Vitest: !`node -p "const p = require('./package.json'); const v = { ...p.dependencies, ...p.devDependencies }.vitest; v ? v : 'NOT found in package.json'" 2>/dev/null || echo "package.json not found"`16- Config: !`found=$(ls vitest.config.* vitest.workspace.* vite.config.* 2>/dev/null); echo "${found:-none found — check package.json or ask the user}"`17- Globals: !`grep -qsE "globals[\"']? *: *true" vitest.config.* vite.config.* 2>/dev/null && echo "enabled — use describe/it/expect/vi without imports" || echo "not detected — import { describe, it, expect, vi } from 'vitest'"`18- Test scripts: !`node -p "Object.entries(require('./package.json').scripts ?? {}).filter(([k]) => k.includes('test')).map(([k, v]) => k + ': ' + v).join(' | ') || 'none'" 2>/dev/null || echo "package.json not found"`1920If anything above is missing or contradicts the repository, read `package.json` and the Vitest config yourself before running tests. Never guess commands.2122## Applicability gate (decide first)2324| Task | TDD required? |25|------|---------------|26| New feature / new behavior | **Yes** |27| Bug fix | **Yes** — failing regression test first |28| Refactoring logic | **Yes** — green tests before, during, after |29| Changing untested legacy code | **Yes** — characterization tests first (see below) |30| Comments, docs, formatting only | No |31| Pure type declarations with no runtime effect | No — consider type tests (`expectTypeOf`) |32| Config / CI / dependency bumps | No — but run the full suite afterwards |3334If the user explicitly opts out of TDD, note the tradeoff in one sentence and follow their instruction. Otherwise this skill is binding.3536## Iron rules37381. **Test first, always.** Production code may only be written in response to a failing test you have *watched* fail.392. **Never start watch mode.** Bare `vitest` (or a `test` script without `run`) can block the session forever. Always use `vitest run …`.403. **Verify Red before Green.** Run the new test and read the output. It must fail for the *expected reason* — an assertion failure describing the missing behavior, not a typo, broken import, or setup error.414. **Minimal Green.** Write the least code that passes. Hard-coding is allowed (仮実装); generality is earned through more tests, not anticipated.425. **Refactor only on green.** Never restructure while any test is red.436. **Never weaken a test to pass.** No deleting, no loosening assertions, no `.skip` without a written reason. If a test is wrong, fix it and say why.447. **One behavior per test.** Name the test after the behavior — `it('returns 0 for an empty cart')` — never after the implementation.458. **Run tests after every Green and every Refactor step.** Affected tests during the cycle, the full suite before declaring done.469. **Leave no `.only`.** Search and remove before finishing (CI rejects it via `--allowOnly=false`).4710. **Tests are first-class code.** Same naming and readability standards — but DAMP over DRY: duplication in tests is fine when it makes intent clearer.4849## The cycle5051```52探索 Explore → Plan (test list) → 🔴 Red → 🟢 Green → 🔵 Refactor → next item … → Done53```5455### 探索 Explore5657Read the relevant code, types, and existing tests before planning. If an unfamiliar API needs investigation, a throwaway spike is allowed — but spike code must be deleted or rebuilt test-first before integration. Never promote untested spike code to production.5859### Plan — write the test list60611. Decompose the task into small, independently verifiable behaviors.622. Order them simplest and most fundamental first. Include edge cases: empty, zero, negative, boundaries, error paths.633. Record the list as `it.todo(…)` placeholders in the test file and mirror it in your task tracker.644. The list is alive: append discoveries as you go; never silently drop an item.6566```typescript67describe('calculateTotal', () => {68 it.todo('returns 0 for an empty cart');69 it.todo('sums item prices');70 it.todo('applies percentage discount');71 it.todo('never returns a negative total');72});73```7475### 🔴 Red — one failing test76771. Pick **one** item from the list.782. Write the test **assert-first**: write the `expect` line, then work backwards to the setup. Expected values are concrete literals — never computed with the logic you are about to implement.793. If the code under test does not exist yet, add only a minimal skeleton (exported stub with the right signature) so the failure is an **assertion failure**, not a compile error.804. Run only that test:81 - `npx vitest run src/cart.test.ts`82 - `npx vitest run -t "applies percentage discount"`835. **Gate:** read the failure output and confirm it fails for the expected reason. If the test passes immediately, stop — either the behavior already exists or the test asserts nothing. Investigate before proceeding.8485### 🟢 Green — minimal pass8687Pick the smallest workable strategy:8889| Strategy | When | How |90|----------|------|-----|91| 仮実装 (Fake It) | Unsure how to implement | Return a hard-coded constant; generalize later |92| 三角測量 (Triangulation) | The fake must become real | Add a second test with different values the constant cannot satisfy, then generalize |93| 明白な実装 (Obvious Implementation) | Implementation is trivial and certain | Just write it — if it does not go green immediately, fall back to smaller steps |9495Run the affected tests. **Gate:** the new test passes and nothing else broke. Speed to green beats elegance — sins committed here are repaid in Refactor.9697### 🔵 Refactor — clean up on green98991. Remove duplication — including duplication **between test and production code** (the same `90` appearing in both is the signal to generalize a fake).1002. Improve names, extract functions, simplify structure. Refactor test code too.1013. After **each** small step, rerun affected tests. Green → continue. Red → revert that step immediately; do not debug forward.1024. Tidy First: when committing, keep structural changes (rename / move / extract) in separate commits from behavioral changes. Commit only on green.103104Then return to the list and pick the next item.105106### Done — definition of done107108- [ ] Every test-list item is implemented or explicitly deferred as `it.todo` (tell the user which)109- [ ] Full suite green: `npx vitest run`110- [ ] No `.only`; every `.skip` carries a reason comment111- [ ] Typecheck passes (`tsc --noEmit` or the project's script); lint passes if configured112- [ ] Test names alone read as a specification of the new behavior113114## When stuck115116- **No green after ~3 attempts** → the step is too big. Revert to the last green state, split the behavior smaller, retry.117- **Refactor broke a test** → revert the refactoring and take a smaller step. Never debug forward on red.118- **Unrelated tests started failing** → stop; treat that as the current Red. No shotgun fixes.119- **Flaky test** → quarantine with `it.skip` + a reason comment + a list item, and report it. Never delete it.120- **Genuinely blocked** → report the test-list state honestly instead of forcing green.121122## Progress reporting123124Report each cycle compactly so the user can follow along:125126```127🔴 applies percentage discount — fails as expected (expected 90, received 100)128🟢 pass (7 tests, 112ms) — fake-it with constant129🔵 generalized via triangulation; extracted applyDiscount() — all green130```131132## Untested legacy code133134Before changing behavior in code that has no tests: pin the *current* behavior with **characterization tests** — assert what the code actually does today, even if it looks wrong, and report oddities to the user instead of silently "fixing" them. Once green, proceed with the normal cycle.135136## References (load on demand)137138- [vitest.md](references/vitest.md) — agent-safe CLI usage, modifiers, matchers, async patterns, type tests, snapshots, coverage139- [test-style.md](references/test-style.md) — readable test patterns: AAA, naming, `it.each`, fixtures, assertion style140- [mocking.md](references/mocking.md) — test-double policy and `vi.*` usage: mock boundaries, not your own logic141- [anti-patterns.md](references/anti-patterns.md) — catalog of TDD violations with corrections