Test-Driven Development (TDD) expertise covering red-green-refactor cycle, behavior-driven development, test-first design, refactoring with confidence, TDD best practices, TDD workflow, unit testing strategies, mock-driven development, test doubles, TDD patterns, SOLID principles through testing, emergent design, incremental development, TDD anti-patterns, and production-grade TDD practices. Activates for TDD, test-driven development, red-green-refactor, test-first, behavior-driven, BDD, refactoring, test doubles, mock-driven, test design, SOLID principles, emergent design, incremental development, TDD workflow, TDD best practices, TDD patterns, Kent Beck, Robert Martin, Uncle Bob, test-first design.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Self-contained TDD expertise for ANY user project.
Goal: Define expected behavior through a failing test
import { describe, it, expect } from 'vitest';
import { Calculator } from './Calculator';
describe('Calculator', () => {
it('should add two numbers', () => {
const calculator = new Calculator();
expect(calculator.add(2, 3)).toBe(5); // WILL FAIL - Calculator doesn't exist
});
});
RED Checklist:
Goal: Simplest code that makes test pass
// Calculator.ts
export class Calculator {
add(a: number, b: number): number {
return a + b; // Minimal implementation
}
}
GREEN Checklist:
Goal: Improve code quality without changing behavior
// Refactor: Support variable arguments
export class Calculator {
add(...numbers: number[]): number {
return numbers.reduce((sum, n) => sum + n, 0);
}
}
// Tests still pass!
REFACTOR Checklist:
Design Benefits:
Quality Benefits:
Productivity Benefits:
Extension of TDD with natural language tests
describe('Shopping Cart', () => {
it('should apply 10% discount when total exceeds $100', () => {
// Given: A cart with $120 worth of items
const cart = new ShoppingCart();
cart.addItem({ price: 120, quantity: 1 });
// When: Getting the total
const total = cart.getTotal();
// Then: 10% discount applied
expect(total).toBe(108); // $120 - $12 (10%)
});
});
BDD Benefits:
Before coding, list all tests needed:
Calculator Tests:
- [ ] add two positive numbers
- [ ] add negative numbers
- [ ] add zero
- [ ] add multiple numbers
- [ ] multiply two numbers
- [ ] divide two numbers
- [ ] divide by zero (error)
Work through list one by one.
Start with hardcoded returns, generalize later:
// Test 1: add(2, 3) = 5
add(a, b) { return 5; } // Hardcoded!
// Test 2: add(5, 7) = 12
add(a, b) { return a + b; } // Generalized
Use multiple tests to force generalization:
// Test 1
expect(fizzbuzz(3)).toBe('Fizz');
// Test 2
expect(fizzbuzz(5)).toBe('Buzz');
// Test 3
expect(fizzbuzz(15)).toBe('FizzBuzz');
// Forces complete implementation
Create test helpers for complex objects:
class UserBuilder {
private user = { name: 'Test', email: 'test@example.com', role: 'user' };
withName(name: string) {
this.user.name = name;
return this;
}
withRole(role: string) {
this.user.role = role;
return this;
}
build() {
return this.user;
}
}
// Usage
const admin = new UserBuilder().withRole('admin').build();
The TDD Safety Net
1. Extract Method:
// Before
function processOrder(order) {
const total = order.items.reduce((sum, item) => sum + item.price, 0);
const tax = total * 0.1;
return total + tax;
}
// After (refactored with test safety)
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
function calculateTax(total) {
return total * 0.1;
}
function processOrder(order) {
const total = calculateTotal(order.items);
const tax = calculateTax(total);
return total + tax;
}
2. Remove Duplication:
// Tests force you to see duplication
it('should validate email', () => {
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('invalid')).toBe(false);
});
it('should validate phone', () => {
expect(validatePhone('+1-555-0100')).toBe(true);
expect(validatePhone('invalid')).toBe(false);
});
// Extract common validation pattern
1. All tests GREEN? → Continue
2. Identify code smell
3. Make small refactoring
4. Run tests → GREEN? → Continue
5. Repeat until satisfied
6. Commit
// BAD: Testing private method
it('should call _validateEmail internally', () => {
spyOn(service, '_validateEmail');
service.createUser({ email: 'test@example.com' });
expect(service._validateEmail).toHaveBeenCalled();
});
// GOOD: Testing behavior
it('should reject invalid email', () => {
expect(() => service.createUser({ email: 'invalid' }))
.toThrow('Invalid email');
});
// Wrong order!
1. Write implementation
2. Write tests
// Correct TDD:
1. Write test (RED)
2. Write implementation (GREEN)
3. Refactor
// BAD: Testing multiple behaviors
it('should handle user lifecycle', () => {
const user = createUser();
updateUser(user, { name: 'New Name' });
deleteUser(user);
// Too much in one test!
});
// GOOD: One behavior per test
it('should create user', () => {
const user = createUser();
expect(user).toBeDefined();
});
it('should update user name', () => {
const user = createUser();
updateUser(user, { name: 'New Name' });
expect(user.name).toBe('New Name');
});
// Don't skip refactoring!
RED → GREEN → REFACTOR → RED → GREEN → REFACTOR
↑________________↑
Always refactor!
When testing with external dependencies
class UserService {
constructor(private db: Database) {} // Inject dependency
async getUser(id: string) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
}
// Test with mock
const mockDb = { query: vi.fn().mockResolvedValue({ id: '123' }) };
const service = new UserService(mockDb);
interface EmailService {
send(to: string, subject: string, body: string): Promise<void>;
}
class MockEmailService implements EmailService {
sent: any[] = [];
async send(to: string, subject: string, body: string) {
this.sent.push({ to, subject, body });
}
}
// Test with mock
const mockEmail = new MockEmailService();
const service = new UserService(mockEmail);
await service.registerUser({ email: 'test@example.com' });
expect(mockEmail.sent).toHaveLength(1);
TDD naturally leads to SOLID design
Tests reveal when class does too much:
// Many tests for one class? Split it!
describe('UserManager', () => {
// 20+ tests here → Too many responsibilities
});
// Refactor to multiple classes
describe('UserCreator', () => { /* 5 tests */ });
describe('UserValidator', () => { /* 5 tests */ });
describe('UserNotifier', () => { /* 5 tests */ });
Tests enable extension without modification:
// Testable, extensible design
interface PaymentProcessor {
process(amount: number): Promise<void>;
}
class StripeProcessor implements PaymentProcessor { }
class PayPalProcessor implements PaymentProcessor { }
TDD requires dependency injection:
// Testable: Depends on abstraction
class OrderService {
constructor(private payment: PaymentProcessor) {}
}
// Easy to test with mocks
const mockPayment = new MockPaymentProcessor();
const service = new OrderService(mockPayment);
1. Write test (RED) → Fails ✅
2. Minimal code (GREEN) → Passes ✅
3. Refactor → Still passes ✅
4. Repeat
✅ New features ✅ Bug fixes (add test first) ✅ Refactoring ✅ Complex logic ✅ Public APIs
❌ Throwaway prototypes ❌ UI layout (use E2E instead) ❌ Highly experimental code
This skill is self-contained and works in ANY user project.