home / skills / yanko-belov / code-craft / tdd
This skill enforces test-first design by guiding you to write failing tests before implementing new features, improving interface thinking and code quality.
npx playbooks add skill yanko-belov/code-craft --skill tddReview the files below or copy the command above to add this skill to your agents.
---
name: test-driven-development
description: Use when implementing any new feature or function. Use when asked to "add tests later". Use when writing code before tests.
---
# Test-Driven Development (TDD)
## Overview
**Write the test first. Watch it fail. Then write the code.**
TDD is not about testing - it's about design. Writing tests first forces you to think about the interface before the implementation.
## When to Use
- Implementing any new function, method, or feature
- Asked to "write code now, add tests later"
- Fixing a bug (write test that reproduces it first)
- Refactoring existing code
## The Iron Rule
```
NEVER write production code without a failing test first.
```
**No exceptions:**
- Not for "it's a simple function"
- Not for "we'll add tests later"
- Not for "I already know how to implement it"
- Not for "tests slow me down"
## The TDD Cycle
```
RED → GREEN → REFACTOR → REPEAT
```
1. **RED**: Write a failing test
2. **GREEN**: Write minimal code to pass
3. **REFACTOR**: Clean up while tests pass
4. **REPEAT**: Next test case
## Detection: Code-First Smell
If you're about to write implementation without a test, STOP:
```typescript
// ❌ VIOLATION: Implementation first
function calculateDiscount(total: number): number {
return total > 100 ? total * 0.1 : 0;
}
// "We'll add tests later if needed"
// ✅ CORRECT: Test first
describe('calculateDiscount', () => {
it('returns 10% discount for orders over $100', () => {
expect(calculateDiscount(150)).toBe(15);
});
it('returns 0 for orders $100 or less', () => {
expect(calculateDiscount(100)).toBe(0);
expect(calculateDiscount(50)).toBe(0);
});
});
// NOW write the implementation
function calculateDiscount(total: number): number {
return total > 100 ? total * 0.1 : 0;
}
```
## Why Test-First Matters
| Test-After | Test-First |
|------------|------------|
| Tests verify what code does | Tests define what code should do |
| Implementation drives design | Requirements drive design |
| Tests often skipped | Tests always exist |
| Hard to test = poor design | Hard to test = caught early |
| "Does it work?" | "Is it right?" |
## Pressure Resistance Protocol
### 1. "We'll Add Tests Later"
**Pressure:** "Just write the function, we can test it later"
**Response:** "Later" never comes. Tests written after are weaker and often skipped entirely.
**Action:** Write the test now. It takes 30 seconds.
### 2. "It's Too Simple to Test"
**Pressure:** "This function is trivial, testing is overkill"
**Response:** Simple functions grow complex. Simple bugs cause outages. Test everything.
**Action:** Write the test. Simple functions = simple tests.
### 3. "I Know How to Implement It"
**Pressure:** "I already have the solution in my head"
**Response:** TDD isn't about uncertainty - it's about capturing requirements as executable specs.
**Action:** Write the test first. Prove your mental model is correct.
### 4. "Tests Slow Me Down"
**Pressure:** "Writing tests takes extra time"
**Response:** Debugging takes 10x longer than testing. TDD is faster overall.
**Action:** The test you write now saves hours of debugging later.
## Red Flags - STOP and Reconsider
- Writing any function without a test
- "Let me just get it working first"
- Implementation file open without test file
- Committing code without corresponding tests
- Tests that pass immediately (never saw them fail)
**All of these mean: Stop. Write the test first.**
## Quick Reference
| Situation | TDD Response |
|-----------|--------------|
| New feature | Write failing test → implement |
| Bug fix | Write test that reproduces bug → fix |
| Refactor | Ensure tests exist → refactor → tests pass |
| "Tests later" | Tests now. Always now. |
## Common Rationalizations (All Invalid)
| Excuse | Reality |
|--------|---------|
| "Tests slow me down" | Debugging slows you down more. |
| "It's too simple" | Simple tests are fast to write. |
| "I'll test later" | You won't. Test now. |
| "I know it works" | Prove it with a test. |
| "Tests are for QA" | Tests are for developers. |
| "Just a prototype" | Prototypes become production. Test them. |
## The Bottom Line
**Test first. Code second. No exceptions.**
The test is the specification. The test is the documentation. The test is the safety net. Write it first, every time.
This skill enforces a test-driven development (TDD) approach for TypeScript work: write the failing test first, then implement minimal code to pass, and refactor while tests remain green. It treats tests as design artifacts that define expected behavior and prevent code-first shortcuts. Use it to keep implementations small, well-specified, and reliably covered by tests.
The skill inspects workflow and coding patterns and reminds you to create a failing test before writing production code. It highlights code-first smells (implementation opened without tests, commits missing tests, tests that never failed) and guides you through the RED → GREEN → REFACTOR cycle. It provides concrete responses to common pressure rationalizations and enforces the rule: never write production code without a failing test first.
What if I’m pressed for time and just need a quick demo?
Write tiny tests that cover the demo’s core behavior; even one failing test prevents design drift and makes later work safer.
How do I handle trivial functions where tests feel redundant?
Trivial functions usually have trivial tests. A one- or two-assert test documents intent and prevents regressions as complexity grows.
What if a test never fails when written?
That’s a red flag. Verify that the test targets executable code and assertions would detect incorrect behavior. Adjust the test so it fails before implementing.