Use when implementing any feature or bugfix, before writing implementation code
Add this skill
npx mdskills install sickn33/test-driven-developmentProvides rigorous, actionable TDD instructions with concrete examples and anti-pattern warnings
1---2name: test-driven-development3description: Use when implementing any feature or bugfix, before writing implementation code4---56# Test-Driven Development (TDD)78## Overview910Write the test first. Watch it fail. Write minimal code to pass.1112**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.1314**Violating the letter of the rules is violating the spirit of the rules.**1516## When to Use1718**Always:**19- New features20- Bug fixes21- Refactoring22- Behavior changes2324**Exceptions (ask your human partner):**25- Throwaway prototypes26- Generated code27- Configuration files2829Thinking "skip TDD just this once"? Stop. That's rationalization.3031## The Iron Law3233```34NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST35```3637Write code before the test? Delete it. Start over.3839**No exceptions:**40- Don't keep it as "reference"41- Don't "adapt" it while writing tests42- Don't look at it43- Delete means delete4445Implement fresh from tests. Period.4647## Red-Green-Refactor4849```dot50digraph tdd_cycle {51 rankdir=LR;52 red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];53 verify_red [label="Verify fails\ncorrectly", shape=diamond];54 green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];55 verify_green [label="Verify passes\nAll green", shape=diamond];56 refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];57 next [label="Next", shape=ellipse];5859 red -> verify_red;60 verify_red -> green [label="yes"];61 verify_red -> red [label="wrong\nfailure"];62 green -> verify_green;63 verify_green -> refactor [label="yes"];64 verify_green -> green [label="no"];65 refactor -> verify_green [label="stay\ngreen"];66 verify_green -> next;67 next -> red;68}69```7071### RED - Write Failing Test7273Write one minimal test showing what should happen.7475<Good>76```typescript77test('retries failed operations 3 times', async () => {78 let attempts = 0;79 const operation = () => {80 attempts++;81 if (attempts < 3) throw new Error('fail');82 return 'success';83 };8485 const result = await retryOperation(operation);8687 expect(result).toBe('success');88 expect(attempts).toBe(3);89});90```91Clear name, tests real behavior, one thing92</Good>9394<Bad>95```typescript96test('retry works', async () => {97 const mock = jest.fn()98 .mockRejectedValueOnce(new Error())99 .mockRejectedValueOnce(new Error())100 .mockResolvedValueOnce('success');101 await retryOperation(mock);102 expect(mock).toHaveBeenCalledTimes(3);103});104```105Vague name, tests mock not code106</Bad>107108**Requirements:**109- One behavior110- Clear name111- Real code (no mocks unless unavoidable)112113### Verify RED - Watch It Fail114115**MANDATORY. Never skip.**116117```bash118npm test path/to/test.test.ts119```120121Confirm:122- Test fails (not errors)123- Failure message is expected124- Fails because feature missing (not typos)125126**Test passes?** You're testing existing behavior. Fix test.127128**Test errors?** Fix error, re-run until it fails correctly.129130### GREEN - Minimal Code131132Write simplest code to pass the test.133134<Good>135```typescript136async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {137 for (let i = 0; i < 3; i++) {138 try {139 return await fn();140 } catch (e) {141 if (i === 2) throw e;142 }143 }144 throw new Error('unreachable');145}146```147Just enough to pass148</Good>149150<Bad>151```typescript152async function retryOperation<T>(153 fn: () => Promise<T>,154 options?: {155 maxRetries?: number;156 backoff?: 'linear' | 'exponential';157 onRetry?: (attempt: number) => void;158 }159): Promise<T> {160 // YAGNI161}162```163Over-engineered164</Bad>165166Don't add features, refactor other code, or "improve" beyond the test.167168### Verify GREEN - Watch It Pass169170**MANDATORY.**171172```bash173npm test path/to/test.test.ts174```175176Confirm:177- Test passes178- Other tests still pass179- Output pristine (no errors, warnings)180181**Test fails?** Fix code, not test.182183**Other tests fail?** Fix now.184185### REFACTOR - Clean Up186187After green only:188- Remove duplication189- Improve names190- Extract helpers191192Keep tests green. Don't add behavior.193194### Repeat195196Next failing test for next feature.197198## Good Tests199200| Quality | Good | Bad |201|---------|------|-----|202| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` |203| **Clear** | Name describes behavior | `test('test1')` |204| **Shows intent** | Demonstrates desired API | Obscures what code should do |205206## Why Order Matters207208**"I'll write tests after to verify it works"**209210Tests written after code pass immediately. Passing immediately proves nothing:211- Might test wrong thing212- Might test implementation, not behavior213- Might miss edge cases you forgot214- You never saw it catch the bug215216Test-first forces you to see the test fail, proving it actually tests something.217218**"I already manually tested all the edge cases"**219220Manual testing is ad-hoc. You think you tested everything but:221- No record of what you tested222- Can't re-run when code changes223- Easy to forget cases under pressure224- "It worked when I tried it" ≠ comprehensive225226Automated tests are systematic. They run the same way every time.227228**"Deleting X hours of work is wasteful"**229230Sunk cost fallacy. The time is already gone. Your choice now:231- Delete and rewrite with TDD (X more hours, high confidence)232- Keep it and add tests after (30 min, low confidence, likely bugs)233234The "waste" is keeping code you can't trust. Working code without real tests is technical debt.235236**"TDD is dogmatic, being pragmatic means adapting"**237238TDD IS pragmatic:239- Finds bugs before commit (faster than debugging after)240- Prevents regressions (tests catch breaks immediately)241- Documents behavior (tests show how to use code)242- Enables refactoring (change freely, tests catch breaks)243244"Pragmatic" shortcuts = debugging in production = slower.245246**"Tests after achieve the same goals - it's spirit not ritual"**247248No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"249250Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.251252Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).25325430 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.255256## Common Rationalizations257258| Excuse | Reality |259|--------|---------|260| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |261| "I'll test after" | Tests passing immediately prove nothing. |262| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |263| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |264| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |265| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |266| "Need to explore first" | Fine. Throw away exploration, start with TDD. |267| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |268| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |269| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |270| "Existing code has no tests" | You're improving it. Add tests for existing code. |271272## Red Flags - STOP and Start Over273274- Code before test275- Test after implementation276- Test passes immediately277- Can't explain why test failed278- Tests added "later"279- Rationalizing "just this once"280- "I already manually tested it"281- "Tests after achieve the same purpose"282- "It's about spirit not ritual"283- "Keep as reference" or "adapt existing code"284- "Already spent X hours, deleting is wasteful"285- "TDD is dogmatic, I'm being pragmatic"286- "This is different because..."287288**All of these mean: Delete code. Start over with TDD.**289290## Example: Bug Fix291292**Bug:** Empty email accepted293294**RED**295```typescript296test('rejects empty email', async () => {297 const result = await submitForm({ email: '' });298 expect(result.error).toBe('Email required');299});300```301302**Verify RED**303```bash304$ npm test305FAIL: expected 'Email required', got undefined306```307308**GREEN**309```typescript310function submitForm(data: FormData) {311 if (!data.email?.trim()) {312 return { error: 'Email required' };313 }314 // ...315}316```317318**Verify GREEN**319```bash320$ npm test321PASS322```323324**REFACTOR**325Extract validation for multiple fields if needed.326327## Verification Checklist328329Before marking work complete:330331- [ ] Every new function/method has a test332- [ ] Watched each test fail before implementing333- [ ] Each test failed for expected reason (feature missing, not typo)334- [ ] Wrote minimal code to pass each test335- [ ] All tests pass336- [ ] Output pristine (no errors, warnings)337- [ ] Tests use real code (mocks only if unavoidable)338- [ ] Edge cases and errors covered339340Can't check all boxes? You skipped TDD. Start over.341342## When Stuck343344| Problem | Solution |345|---------|----------|346| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |347| Test too complicated | Design too complicated. Simplify interface. |348| Must mock everything | Code too coupled. Use dependency injection. |349| Test setup huge | Extract helpers. Still complex? Simplify design. |350351## Debugging Integration352353Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.354355Never fix bugs without a test.356357## Testing Anti-Patterns358359When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls:360- Testing mock behavior instead of real behavior361- Adding test-only methods to production classes362- Mocking without understanding dependencies363364## Final Rule365366```367Production code → test exists and failed first368Otherwise → not TDD369```370371No exceptions without your human partner's permission.372
Full transparency — inspect the skill content before installing.