---
name: Test-Driven Development
description: Use this skill when implementing new features, fixing bugs, or refactoring code. Enforces strict RED-GREEN-REFACTOR discipline. Invoke before writing ANY production code.
version: 1.0.0
estimated_tokens: 350
---

# Test-Driven Development (TDD)

> Write the test first. Watch it fail. Then make it pass. No exceptions.

> Adapted from obra/superpowers TDD enforcement discipline.

---

## When To Use This Skill

- Implementing any new function, method, or feature
- Fixing any bug (write the failing test that exposes the bug FIRST)
- Refactoring existing code (ensure tests exist before changing anything)
- Any time someone says "just write the code, we'll test later" (especially then)

## When NOT To Use This Skill

- Prototyping/spike work (explicitly labeled as throwaway)
- Configuration files (no logic to test)
- Pure UI layout changes with no logic

---

## The Iron Rule

**If you wrote production code before writing a test, DELETE IT and start over.**

This is not a suggestion. This is the discipline. The moment you write code before a test, you are no longer doing TDD -- you are doing "testing after" which catches fewer bugs and produces worse designs.

---

## The RED-GREEN-REFACTOR Cycle

```
Step 1: RED    -- Write a test that FAILS
                  (If it passes immediately, you wrote the wrong test)
                  
Step 2: GREEN  -- Write the MINIMUM code to make the test pass
                  (No extra features. No "while I'm here" additions.
                   Hardcode the return value if that passes the test.)
                  
Step 3: REFACTOR -- Clean up without changing behavior
                    (All tests must still pass after refactoring)
                  
Step 4: REPEAT -- Next test case
```

### Critical: What "Minimum" Means

| Phase | Allowed | NOT Allowed |
|-------|---------|-------------|
| RED | Write one failing test | Write multiple tests at once |
| GREEN | Hardcode return value to pass | Write the "real" implementation |
| GREEN | Write simplest possible logic | Add error handling for cases not yet tested |
| REFACTOR | Extract methods, rename, reorganize | Add new behavior (that needs a new test first) |

---

## TDD Workflow for Features

### 1. List the Test Cases First

Before writing ANY code:

```
Feature: User discount calculation

Test cases:
1. No discount for orders under $50
2. 10% discount for orders $50-$99
3. 20% discount for orders $100+
4. Zero amount returns zero discount
5. Negative amount throws error
```

### 2. Implement One Test at a Time

```typescript
// RED: Write failing test
test('no discount for orders under $50', () => {
  expect(calculateDiscount(30)).toBe(0);
});
// RUN: npm test → FAIL (calculateDiscount doesn't exist)

// GREEN: Minimum code to pass
function calculateDiscount(amount: number): number {
  return 0; // Yes, hardcode. This passes the test.
}
// RUN: npm test → PASS

// RED: Next test forces real implementation
test('10% discount for orders $50-$99', () => {
  expect(calculateDiscount(80)).toBe(8);
});
// RUN: npm test → FAIL (returns 0, not 8)

// GREEN: Now we need real logic
function calculateDiscount(amount: number): number {
  if (amount >= 50) return amount * 0.1;
  return 0;
}
// RUN: npm test → PASS (both tests)

// Continue until all test cases covered...
```

### 3. Refactor Only When Green

After all tests pass:
- Extract constants (`DISCOUNT_THRESHOLD`, `DISCOUNT_RATE`)
- Rename for clarity
- Eliminate duplication
- Run tests after EVERY refactor step

---

## TDD for Bug Fixes

**Every bug fix MUST start with a failing test that reproduces the bug:**

```
1. Receive bug report: "Discount applies to $49.99 orders"
2. Write test: expect(calculateDiscount(49.99)).toBe(0)
3. Run test → It FAILS (confirms the bug exists)
4. Fix the code
5. Run test → It PASSES (confirms the fix works)
6. Run ALL tests → Still pass (confirms no regression)
```

**Why this order matters:**
- If the test passes immediately, you misunderstood the bug
- The test becomes a permanent regression guard
- You have proof the bug existed and proof it's fixed

---

## TDD for Refactoring

**Never refactor without tests. The sequence is:**

```
1. Write tests for current behavior (if none exist)
2. Run tests → All PASS (baseline)
3. Refactor the code
4. Run tests → All still PASS (behavior preserved)
```

If tests don't exist for the code you want to refactor:
- Write characterization tests (tests that document current behavior)
- THEN refactor
- Never skip this step -- "I understand the code" is not a substitute for tests

---

## What to Test (Priority Order)

| Priority | What | Why |
|----------|------|-----|
| **1. Always** | Core business logic | This is where bugs cost money |
| **2. Always** | Edge cases (null, empty, negative, overflow) | This is where bugs hide |
| **3. Always** | Error handling paths | This is where bugs cause outages |
| **4. Usually** | Integration points (API calls, DB queries) | This is where bugs are hardest to debug |
| **5. Sometimes** | UI behavior with logic | Skip pure layout, test interactions |
| **6. Rarely** | Third-party library wrappers | They have their own tests |

---

## Test Quality Standards

### Good Tests Are:

| Property | Meaning | Example |
|----------|---------|---------|
| **Fast** | Runs in milliseconds | Mock external calls |
| **Independent** | No test depends on another | Each test sets up its own state |
| **Repeatable** | Same result every time | No random data, no time-dependency |
| **Self-validating** | Pass or fail, no manual checking | Assert specific values, not "looks right" |
| **Timely** | Written BEFORE the code | Not after the fact |

### Test Naming Convention

```typescript
// Pattern: [unit]_[scenario]_[expected result]
test('calculateDiscount_orderUnder50_returnsZero', () => { ... });
test('calculateDiscount_orderExactly50_returns10Percent', () => { ... });
test('calculateDiscount_negativeAmount_throwsError', () => { ... });
```

---

## Rationalizations to Reject

| Rationalization | Why It's Wrong | Required Action |
|----------------|---------------|----------------|
| "I'll write tests after the code" | You'll write tests that confirm your code, not that challenge it | Delete the code, write the test first |
| "This is too simple to test" | Simple code that breaks causes complex debugging | Write the test -- it'll be fast |
| "I'm just spiking/exploring" | Fine, but label it explicitly and throw it away | Start fresh with TDD when building for real |
| "TDD is slower" | TDD is slower to START, faster to FINISH (fewer bugs, less debugging) | Trust the process |
| "The test is obvious" | If it's obvious, it takes 30 seconds to write | Write it anyway |
| "I need to see the code to know what to test" | You need to understand the REQUIREMENT, not the implementation | Re-read the spec/user story |
| "I already have tests from a similar feature" | Similar tests for similar code still miss unique edge cases | Write tests specific to THIS feature |
| "The type system catches this" | Types catch type errors, not logic errors | Test the logic |

---

## Integration with Other Skills

- **verification/SKILL.md**: Iron Law applies -- run tests and read output before claiming completion
- **code-review/SKILL.md**: Review should verify TDD discipline was followed
- **testing/SKILL.md**: TDD produces the tests; testing skill covers infrastructure and strategy

---

## Pre-Completion Checklist

- [ ] Every feature has tests written BEFORE implementation
- [ ] Every bug fix started with a failing test
- [ ] All tests pass (verified by running them, not by assumption)
- [ ] No production code exists without a corresponding test
- [ ] Test names describe the scenario and expected result
- [ ] Refactoring was done only when all tests were green

---

*This skill enforces discipline. It will feel slow at first. Trust it.*
