Initial commit
This commit is contained in:
454
skills/tdd-expert/SKILL.md
Normal file
454
skills/tdd-expert/SKILL.md
Normal file
@@ -0,0 +1,454 @@
|
||||
---
|
||||
name: tdd-expert
|
||||
description: 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.
|
||||
---
|
||||
|
||||
# Test-Driven Development (TDD) Expert
|
||||
|
||||
**Self-contained TDD expertise for ANY user project.**
|
||||
|
||||
---
|
||||
|
||||
## The TDD Cycle: Red-Green-Refactor
|
||||
|
||||
### 1. RED Phase: Write Failing Test
|
||||
|
||||
**Goal**: Define expected behavior through a failing test
|
||||
|
||||
```typescript
|
||||
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**:
|
||||
- [ ] Test describes ONE specific behavior
|
||||
- [ ] Test fails for RIGHT reason (not syntax error)
|
||||
- [ ] Test name is clear
|
||||
- [ ] Expected behavior obvious
|
||||
|
||||
### 2. GREEN Phase: Minimal Implementation
|
||||
|
||||
**Goal**: Simplest code that makes test pass
|
||||
|
||||
```typescript
|
||||
// Calculator.ts
|
||||
export class Calculator {
|
||||
add(a: number, b: number): number {
|
||||
return a + b; // Minimal implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GREEN Checklist**:
|
||||
- [ ] Test passes
|
||||
- [ ] Code is simplest possible
|
||||
- [ ] No premature optimization
|
||||
- [ ] No extra features
|
||||
|
||||
### 3. REFACTOR Phase: Improve Design
|
||||
|
||||
**Goal**: Improve code quality without changing behavior
|
||||
|
||||
```typescript
|
||||
// Refactor: Support variable arguments
|
||||
export class Calculator {
|
||||
add(...numbers: number[]): number {
|
||||
return numbers.reduce((sum, n) => sum + n, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests still pass!
|
||||
```
|
||||
|
||||
**REFACTOR Checklist**:
|
||||
- [ ] All tests still pass
|
||||
- [ ] Code is more readable
|
||||
- [ ] Removed duplication
|
||||
- [ ] Better design patterns
|
||||
|
||||
---
|
||||
|
||||
## TDD Benefits
|
||||
|
||||
**Design Benefits**:
|
||||
- Forces modular, testable code
|
||||
- Reveals design problems early
|
||||
- Encourages SOLID principles
|
||||
- Promotes simple solutions
|
||||
|
||||
**Quality Benefits**:
|
||||
- 100% test coverage (by definition)
|
||||
- Tests document behavior
|
||||
- Regression safety net
|
||||
- Faster debugging
|
||||
|
||||
**Productivity Benefits**:
|
||||
- Less time debugging
|
||||
- Confidence to refactor
|
||||
- Faster iterations
|
||||
- Clearer requirements
|
||||
|
||||
---
|
||||
|
||||
## BDD: Behavior-Driven Development
|
||||
|
||||
**Extension of TDD with natural language tests**
|
||||
|
||||
### Given-When-Then Pattern
|
||||
|
||||
```typescript
|
||||
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**:
|
||||
- Tests readable by non-developers
|
||||
- Clear business requirements
|
||||
- Better stakeholder communication
|
||||
- Executable specifications
|
||||
|
||||
---
|
||||
|
||||
## TDD Patterns
|
||||
|
||||
### Pattern 1: Test List
|
||||
|
||||
Before coding, list all tests needed:
|
||||
|
||||
```markdown
|
||||
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.
|
||||
|
||||
### Pattern 2: Fake It Till You Make It
|
||||
|
||||
Start with hardcoded returns, generalize later:
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
```
|
||||
|
||||
### Pattern 3: Triangulation
|
||||
|
||||
Use multiple tests to force generalization:
|
||||
|
||||
```typescript
|
||||
// Test 1
|
||||
expect(fizzbuzz(3)).toBe('Fizz');
|
||||
|
||||
// Test 2
|
||||
expect(fizzbuzz(5)).toBe('Buzz');
|
||||
|
||||
// Test 3
|
||||
expect(fizzbuzz(15)).toBe('FizzBuzz');
|
||||
|
||||
// Forces complete implementation
|
||||
```
|
||||
|
||||
### Pattern 4: Test Data Builders
|
||||
|
||||
Create test helpers for complex objects:
|
||||
|
||||
```typescript
|
||||
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();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Refactoring with Confidence
|
||||
|
||||
**The TDD Safety Net**
|
||||
|
||||
### Refactoring Types
|
||||
|
||||
**1. Extract Method**:
|
||||
```typescript
|
||||
// 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**:
|
||||
```typescript
|
||||
// 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
|
||||
```
|
||||
|
||||
### Refactoring Workflow
|
||||
|
||||
```
|
||||
1. All tests GREEN? → Continue
|
||||
2. Identify code smell
|
||||
3. Make small refactoring
|
||||
4. Run tests → GREEN? → Continue
|
||||
5. Repeat until satisfied
|
||||
6. Commit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TDD Anti-Patterns
|
||||
|
||||
### ❌ Testing Implementation Details
|
||||
|
||||
```typescript
|
||||
// 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');
|
||||
});
|
||||
```
|
||||
|
||||
### ❌ Writing Tests After Code
|
||||
|
||||
```typescript
|
||||
// Wrong order!
|
||||
1. Write implementation
|
||||
2. Write tests
|
||||
|
||||
// Correct TDD:
|
||||
1. Write test (RED)
|
||||
2. Write implementation (GREEN)
|
||||
3. Refactor
|
||||
```
|
||||
|
||||
### ❌ Large Tests
|
||||
|
||||
```typescript
|
||||
// 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');
|
||||
});
|
||||
```
|
||||
|
||||
### ❌ Skipping Refactor Phase
|
||||
|
||||
```typescript
|
||||
// Don't skip refactoring!
|
||||
RED → GREEN → REFACTOR → RED → GREEN → REFACTOR
|
||||
↑________________↑
|
||||
Always refactor!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mock-Driven TDD
|
||||
|
||||
**When testing with external dependencies**
|
||||
|
||||
### Strategy 1: Dependency Injection
|
||||
|
||||
```typescript
|
||||
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);
|
||||
```
|
||||
|
||||
### Strategy 2: Interface-Based Mocking
|
||||
|
||||
```typescript
|
||||
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);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SOLID Principles Through TDD
|
||||
|
||||
**TDD naturally leads to SOLID design**
|
||||
|
||||
### Single Responsibility (SRP)
|
||||
Tests reveal when class does too much:
|
||||
```typescript
|
||||
// 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 */ });
|
||||
```
|
||||
|
||||
### Open/Closed (OCP)
|
||||
Tests enable extension without modification:
|
||||
```typescript
|
||||
// Testable, extensible design
|
||||
interface PaymentProcessor {
|
||||
process(amount: number): Promise<void>;
|
||||
}
|
||||
|
||||
class StripeProcessor implements PaymentProcessor { }
|
||||
class PayPalProcessor implements PaymentProcessor { }
|
||||
```
|
||||
|
||||
### Dependency Inversion (DIP)
|
||||
TDD requires dependency injection:
|
||||
```typescript
|
||||
// Testable: Depends on abstraction
|
||||
class OrderService {
|
||||
constructor(private payment: PaymentProcessor) {}
|
||||
}
|
||||
|
||||
// Easy to test with mocks
|
||||
const mockPayment = new MockPaymentProcessor();
|
||||
const service = new OrderService(mockPayment);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### TDD Workflow
|
||||
```
|
||||
1. Write test (RED) → Fails ✅
|
||||
2. Minimal code (GREEN) → Passes ✅
|
||||
3. Refactor → Still passes ✅
|
||||
4. Repeat
|
||||
```
|
||||
|
||||
### Test Smells
|
||||
- Test too long (>20 lines)
|
||||
- Multiple assertions (>3)
|
||||
- Testing implementation
|
||||
- Unclear test name
|
||||
- Slow tests (>100ms)
|
||||
- Flaky tests
|
||||
|
||||
### When to Use TDD
|
||||
✅ 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.**
|
||||
Reference in New Issue
Block a user