← back to Goodquestion Ai
skills/tdd.md
154 lines
# Skill: Test-Driven Development (TDD)
**Use when implementing any feature or bugfix** -- before writing implementation code.
## Overview
Write the test first. Watch it fail. Write minimal code to pass.
**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.
## The Iron Law
```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```
Write code before the test? Delete it. Start over.
## When to Use
**Always:**
- New features
- Bug fixes
- Refactoring
- Behavior changes
**Exceptions (ask first):**
- Throwaway prototypes
- Generated code
- Configuration files
## Red-Green-Refactor
### RED -- Write Failing Test
Write one minimal test showing what should happen.
**Good:**
```typescript
test('retries failed operations 3 times', async () => {
let attempts = 0;
const operation = () => {
attempts++;
if (attempts < 3) throw new Error('fail');
return 'success';
};
const result = await retryOperation(operation);
expect(result).toBe('success');
expect(attempts).toBe(3);
});
```
Clear name, tests real behavior, one thing.
**Bad:**
```typescript
test('retry works', async () => {
// Vague name, tests mock not code
});
```
**Requirements:**
- One behavior per test
- Clear, descriptive name
- Real code (mocks only if unavoidable)
### Verify RED -- Watch It Fail
**MANDATORY. Never skip.**
```bash
npm test path/to/test.test.ts
```
Confirm:
- Test fails (not errors)
- Failure message is expected
- Fails because feature is missing (not typos)
### GREEN -- Minimal Code
Write the simplest code to pass the test. Don't add features, refactor other code, or "improve" beyond the test.
### Verify GREEN -- Watch It Pass
**MANDATORY.**
Confirm:
- Test passes
- Other tests still pass
- Output is clean (no errors, no warnings)
### REFACTOR -- Clean Up
After green only:
- Remove duplication
- Improve names
- Extract helpers
Keep tests green. Don't add behavior.
### Repeat
Next failing test for next feature.
## Why Order Matters
**"I'll write tests after to verify it works"**
Tests written after code pass immediately. Passing immediately proves nothing:
- Might test the wrong thing
- Might test implementation, not behavior
- You never saw it catch the bug
**"Deleting X hours of work is wasteful"**
Sunk cost fallacy. The time is already gone. Working code without real tests is technical debt.
## Good Tests
| Quality | Good | Bad |
|---------|------|-----|
| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain')` |
| **Clear** | Name describes behavior | `test('test1')` |
| **Shows intent** | Demonstrates desired API | Obscures what code should do |
## Common Rationalizations
| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "TDD will slow me down" | TDD is faster than debugging. |
## Debugging Integration
Bug found? Write a failing test reproducing it. Follow the TDD cycle. The test proves the fix and prevents regression.
Never fix bugs without a test.
## Verification Checklist
Before marking work complete:
- [ ] Every new function/method has a test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for expected reason
- [ ] Wrote minimal code to pass each test
- [ ] All tests pass
- [ ] Output is clean
- [ ] Edge cases and errors covered