This skill enforces TDD practices based on the core principle:
"If you didn't watch the test fail, you don't know if it tests the right thing."
When to activate
- For every new feature implementation
- For bug fixes (first a test that reproduces the bug, then the fix)
- For refactoring (tests must pass before AND after the change)
- For API extensions
- For every exported function
The Red-Green-Refactor cycle
1. RED: Write a test that fails
// ZUERST: Test schreiben
describe('calculateOEE', () => {
it('should return 0 when availability is 0', () => {
const result = calculateOEE({ availability: 0, performance: 100, quality: 100 });
expect(result).toBe(0);
});
});
// Test MUSS fehlschlagen:
// Error: calculateOEE is not defined
// ODER
// Error: Expected 0 but received undefined
Important: The test MUST fail for the right reason:
- Function does not exist
- Function returns the wrong result
- NOT: syntax error in the test itself
2. GREEN: Minimal code that passes the test
// DANACH: Minimaler Code
export function calculateOEE(params: OEEParams): number {
if (params.availability === 0) return 0;
// Weitere Logik kommt spaeter durch weitere Tests
return 0;
}
Rule: Write the SIMPLEST code that passes the test.
- No optimizations
- No additional features
- No "obvious" extensions
3. REFACTOR: Clean up without new behavior
// Nach mehreren gruenen Tests: Refactoring erlaubt
export function calculateOEE({ availability, performance, quality }: OEEParams): number {
return (availability * performance * quality) / 10000;
}
Rules for refactoring:
- All existing tests MUST continue to pass
- Do NOT add new behavior
- Only improve code structure
- After every refactoring step: run the tests
The 13 invalid rationalizations
These excuses are NEVER acceptable:
1. "Too simple to test"
Reality: Simple code needs simple tests. 1 line of test is okay.
it('should add two numbers', () => {
expect(add(2, 3)).toBe(5);
});
2. "I'll test later"
Reality: "Later" means "never". TDD means test FIRST.
3. "Already tested manually"
Reality: Manual tests are not reproducible and do not scale.
4. "Time pressure allows no tests"
Reality: Tests save time during debugging and prevent regressions.
5. "Private methods don't need to be tested"
Reality: Test the behavior through public APIs. If not testable: refactor.
6. "UI code can't be tested"
Reality: React Testing Library, Playwright, Storybook exist precisely for that.
7. "The logic is trivial"
Reality: Trivial logic changes. Tests document expected behavior.
8. "We have a QA process"
Reality: QA finds bugs later and more expensively. TDD prevents bugs from the start.
9. "The code is only temporary"
Reality: Temporary code often lives for years. Tests also secure temporary code.
10. "Tests slow down development"
Reality: TDD accelerates in the long term through less debugging and regressions.
11. "Legacy Code has no tests"
Reality: Write characterization tests before changes. Improve step by step.
12. "The framework/library already tests that"
Reality: Test YOUR usage of the framework, not the framework itself.
13. "Mocking is too cumbersome"
Reality: If mocking is too complex, the design is too complex. Refactor.
Verification Checklist
Before every commit the following MUST apply:
- Every exported function has at least one test
- Every test failed BEFORE the implementation (RED observed)
- Every test checks exactly ONE thing (Single Assertion Principle)
- Minimal code per test (no over-engineering)
- Edge cases covered:
- Null/undefined inputs
- Empty arrays/strings
- Boundary values (0, MAX_INT, negative numbers)
- Invalid inputs (TypeError expected)
- Test names describe behavior:
should [erwartetes Ergebnis] when [Bedingung] - No
skiporonlytests in the commit - Coverage at least 80% for new code
TDD Workflow in Practice
Step 1: Create test file
# Fuer neue Funktion in src/utils/oee.ts
touch src/utils/oee.test.ts
Step 2: Minimal failing test
// src/utils/oee.test.ts
import { describe, it, expect } from 'vitest';
import { calculateOEE } from './oee';
describe('calculateOEE', () => {
it('should return 85 for sample manufacturing data', () => {
const result = calculateOEE({
availability: 90,
performance: 95,
quality: 99
});
expect(result).toBeCloseTo(84.645, 2);
});
});
Step 3: Run test (MUST fail)
npm run test -- --run src/utils/oee.test.ts
# Erwartete Ausgabe:
# Error: Cannot find module './oee'
# ODER nach Stub:
# Error: Expected 84.645 but received undefined
Step 4: Minimal implementation
// src/utils/oee.ts
export interface OEEParams {
availability: number;
performance: number;
quality: number;
}
export function calculateOEE(params: OEEParams): number {
return (params.availability * params.performance * params.quality) / 10000;
}
Step 5: Run test (MUST pass)
npm run test -- --run src/utils/oee.test.ts
# Erwartete Ausgabe:
# PASS src/utils/oee.test.ts
Step 6: Next test for edge case
it('should throw when values exceed 100', () => {
expect(() => calculateOEE({
availability: 101,
performance: 100,
quality: 100
})).toThrow('Values must be between 0 and 100');
});
Back to Step 3 (RED) -> Step 4 (GREEN) -> Repeat.
Recognize Anti-Patterns
FORBIDDEN: Test after code
// FALSCH: Code zuerst geschrieben
function add(a: number, b: number): number {
return a + b;
}
// Dann erst Test geschrieben - UNGUELTIG!
// Du weisst nicht ob der Test das richtige testet
FORBIDDEN: Too much code at once
// FALSCH: Komplette Klasse ohne Tests implementiert
class UserService {
async create(user: User) { ... }
async update(id: string, data: Partial<User>) { ... }
async delete(id: string) { ... }
async findById(id: string) { ... }
async findAll(filters: FilterOptions) { ... }
}
// RICHTIG: Eine Methode nach der anderen mit TDD
FORBIDDEN: Adjust tests so that they pass
// FALSCH: Test geaendert weil Implementation anders ist
// Vorher: expect(result).toBe(100);
// Nachher: expect(result).toBe(99.5); // "Weil die Formel das so ausgibt"
// RICHTIG: Implementation korrigieren ODER Anforderungen klaeren
Framework-Specific Patterns
React Components (Vitest + Testing Library)
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('LoginButton', () => {
it('should show loading spinner when clicked', async () => {
render(<LoginButton onLogin={vi.fn()} />);
await userEvent.click(screen.getByRole('button', { name: /login/i }));
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
});
API Endpoints (Supertest)
import request from 'supertest';
import { app } from '../app';
describe('POST /api/analyze', () => {
it('should return 401 without authentication', async () => {
const response = await request(app)
.post('/api/analyze')
.send({ data: 'test' });
expect(response.status).toBe(401);
expect(response.body.error).toBe('Unauthorized');
});
});
Async Code
describe('fetchUserData', () => {
it('should retry 3 times on network failure', async () => {
const mockFetch = vi.fn()
.mockRejectedValueOnce(new Error('Network'))
.mockRejectedValueOnce(new Error('Network'))
.mockResolvedValueOnce({ id: 1, name: 'Test' });
const result = await fetchUserData(1, { fetch: mockFetch });
expect(mockFetch).toHaveBeenCalledTimes(3);
expect(result.name).toBe('Test');
});
});
In Case of Violation of TDD
- STOP: No further code generation without test
- WARNING: "TDD violation detected: [Description]"
- INSTRUCTION: Show the correct TDD workflow
- QUESTION: "Should I write the test first?"
Metrics for Measuring Success
- Test-to-Code Ratio: At least 1:1 (test LOC to code LOC)
- Coverage: Minimum 80% for new code
- Red-Green Time: Short cycles (5-10 minutes per feature)
- Test Execution Time: Unit tests under 10 seconds
Commands
# Einzelnen Test laufen lassen (RED pruefen)
npm run test -- --run path/to/file.test.ts
# Alle Tests (vor Commit)
npm run test
# Coverage Report
npm run test:coverage
# Watch Mode (waehrend Entwicklung)
npm run test -- --watch
Integration with Other Skills
- code-quality-gate: TDD tests are part of Gate 1 (Pre-Commit)
- strict-typescript-mode: Tests must also be type-safe
- supervisor: Verifier agent verifies TDD compliance
Sources and Further Reading
- Kent Beck: "Test-Driven Development: By Example"
- Robert C. Martin: "Clean Code" (Chapter 9: Unit Tests)
- Martin Fowler: "Refactoring" (Test safety in refactoring)