| name | test-driven-development |
| description | Red-Green-Refactor workflow: write tests before implementation, maintain coverage. Use when implementing logic, fixing bugs, or changing behavior. |
| metadata | {"category":"user-invoked"} |
| disable-model-invocation | true |
Default output: return only the result, blockers, and required evidence. Omit preambles, process narration, repeated context, confidence scores, and follow-up offers. Use at most five bullets unless a required artifact or schema needs more.
Test-Driven Development
Overview
Write a failing test first, then implement the minimum code to pass, then refactor. Tests are proof that the system behaves as intended. This is the canonical Build-phase testing skill.
When to Use
- Implementing new logic or features
- Fixing bugs (write a failing test that reproduces the bug)
- Refactoring existing behavior
- Changing any code that has correctness requirements
Test Pyramid
- 80% Unit tests: fast, isolated, test individual functions and components
- 15% Integration tests: test interactions between modules
- 5% End-to-end tests: test critical user flows through the full stack
TDD Cycle
Red: Write a Failing Test
- Define the expected behavior in a test
- Run the test and confirm it fails for the right reason
- The test should describe what you want, not how to implement it
Green: Make It Pass
- Write the minimum implementation to pass the test
- Do not add functionality beyond what the test requires
- It is acceptable for the implementation to be ugly at this stage
Refactor: Improve Without Changing Behavior
- Clean up the implementation
- Extract helpers, improve names, remove duplication
- Run all tests after each refactor step
Test Quality Rules
DAMP Over DRY in Tests
Tests should be Descriptive And Meaningful Phrases. Prefer readability over test-code abstraction. Duplication in test setup is acceptable when it makes the test self-contained.
The Beyonce Rule
If you liked it, you should have put a test on it. If behavior matters, a test should verify it.
Test Naming
- Describe the scenario and expected outcome
it('should return 404 when user does not exist')
- Not
it('works')
Test Isolation
- Each test should set up its own state
- No shared mutable state between tests
- Tests should be runnable in any order
Process
Step 1: Define Interfaces First
Before writing tests, define the public interface:
- Function signatures
- Type definitions
- Error types
Step 2: Write the First Test
- Test the simplest happy path
- Run it and watch it fail
Step 3: Implement and Iterate
- Make the test pass
- Write the next test for the next behavior
- Repeat until all acceptance criteria are covered
Step 4: Verify Coverage
- Run coverage report
- Ensure 80%+ line coverage
- Ensure 100% coverage on critical paths
- Identify untested edge cases and add tests
Common Rationalizations
| Rationalization | Reality |
|---|
| "I will add tests later" | Later never comes. Untested code is unverifiable code. |
| "TDD slows me down" | TDD slows down typing but speeds up delivery by reducing debugging time. |
| "This is too simple to test" | Simple code can have simple bugs. Simple tests are cheap to write. |
| "I need to implement first to know what to test" | Defining expected behavior first is exactly the point. |
Verification
Anti-Rationalization Table
| Excuse | Counter |
|---|
| "I'll add tests after the code works" | Tests written after code are biased to the implementation. TDD drives better design. |
| "This function is too simple to test" | Simple functions become complex. Tests document expected behavior and catch regressions. |
| "Testing takes too much time" | Untested code takes more time debugging. Tests are an investment, not a cost. |
| "The manual testing is enough" | Manual testing isn't repeatable. Automated tests catch regressions that manual testing misses. |
| "I need to implement first to know what to test" | Defining expected behavior first is exactly the point. Tests specify behavior before code. |