specweave 0.26.4 → 0.26.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +154 -4
- package/bin/specweave.js +15 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js +2 -2
- package/dist/plugins/specweave-github/lib/completion-calculator.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +28 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +191 -19
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts +3 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +25 -2
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -1
- package/dist/src/cli/commands/archive.d.ts +10 -0
- package/dist/src/cli/commands/archive.d.ts.map +1 -0
- package/dist/src/cli/commands/archive.js +78 -0
- package/dist/src/cli/commands/archive.js.map +1 -0
- package/dist/src/cli/commands/init.js +2 -2
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/initial-increment-generator.js +48 -8
- package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -1
- package/dist/src/core/increment/increment-reopener.d.ts.map +1 -1
- package/dist/src/core/increment/increment-reopener.js +13 -14
- package/dist/src/core/increment/increment-reopener.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +19 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts +85 -0
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -0
- package/dist/src/core/increment/status-change-sync-trigger.js +137 -0
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -0
- package/dist/src/core/increment/sync-circuit-breaker.d.ts +64 -0
- package/dist/src/core/increment/sync-circuit-breaker.d.ts.map +1 -0
- package/dist/src/core/increment/sync-circuit-breaker.js +95 -0
- package/dist/src/core/increment/sync-circuit-breaker.js.map +1 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts +12 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +157 -24
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/init/repo/types.d.ts +1 -1
- package/package.json +2 -2
- package/plugins/specweave/agents/pm/AGENT.md +13 -7
- package/plugins/specweave/commands/sync-diagnostics.md +227 -0
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +20 -8
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +19 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/skills/brownfield-analyzer/SKILL.md +267 -868
- package/plugins/specweave/skills/increment-planner/SKILL.md +379 -1245
- package/plugins/specweave/skills/role-orchestrator/SKILL.md +293 -969
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/ado-multi-project-sync.js +1 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-docs/skills/technical-writing/SKILL.md +333 -839
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1080 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-github/lib/completion-calculator.js +1 -1
- package/plugins/specweave-github/lib/completion-calculator.ts +2 -2
- package/plugins/specweave-github/lib/github-feature-sync.js +152 -18
- package/plugins/specweave-github/lib/github-feature-sync.ts +225 -22
- package/plugins/specweave-github/lib/user-story-issue-builder.js +21 -1
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +31 -3
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +981 -0
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
- package/plugins/specweave-testing/skills/tdd-expert/SKILL.md +269 -749
- package/plugins/specweave-testing/skills/unit-testing-expert/SKILL.md +318 -810
|
@@ -5,17 +5,15 @@ description: Test-Driven Development (TDD) expertise covering red-green-refactor
|
|
|
5
5
|
|
|
6
6
|
# Test-Driven Development (TDD) Expert
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Self-contained TDD expertise for ANY user project.**
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
**Core Principle**: "Red, Green, Refactor"
|
|
10
|
+
---
|
|
13
11
|
|
|
14
|
-
## The TDD Cycle
|
|
12
|
+
## The TDD Cycle: Red-Green-Refactor
|
|
15
13
|
|
|
16
|
-
### 1.
|
|
14
|
+
### 1. RED Phase: Write Failing Test
|
|
17
15
|
|
|
18
|
-
**Goal**: Define expected behavior through a test
|
|
16
|
+
**Goal**: Define expected behavior through a failing test
|
|
19
17
|
|
|
20
18
|
```typescript
|
|
21
19
|
import { describe, it, expect } from 'vitest';
|
|
@@ -24,911 +22,433 @@ import { Calculator } from './Calculator';
|
|
|
24
22
|
describe('Calculator', () => {
|
|
25
23
|
it('should add two numbers', () => {
|
|
26
24
|
const calculator = new Calculator();
|
|
27
|
-
|
|
28
|
-
// This test WILL fail because Calculator doesn't exist yet
|
|
29
|
-
expect(calculator.add(2, 3)).toBe(5);
|
|
25
|
+
expect(calculator.add(2, 3)).toBe(5); // WILL FAIL - Calculator doesn't exist
|
|
30
26
|
});
|
|
31
27
|
});
|
|
32
28
|
```
|
|
33
29
|
|
|
34
|
-
**
|
|
30
|
+
**RED Checklist**:
|
|
35
31
|
- [ ] Test describes ONE specific behavior
|
|
36
|
-
- [ ] Test fails for
|
|
37
|
-
- [ ] Test is
|
|
38
|
-
- [ ] Expected behavior
|
|
32
|
+
- [ ] Test fails for RIGHT reason (not syntax error)
|
|
33
|
+
- [ ] Test name is clear
|
|
34
|
+
- [ ] Expected behavior obvious
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
- Writing multiple assertions in one test
|
|
42
|
-
- Testing implementation details instead of behavior
|
|
43
|
-
- Writing tests that pass immediately (no value!)
|
|
44
|
-
- Unclear test names
|
|
36
|
+
### 2. GREEN Phase: Minimal Implementation
|
|
45
37
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**Goal**: Write the simplest code that makes the test pass
|
|
38
|
+
**Goal**: Simplest code that makes test pass
|
|
49
39
|
|
|
50
40
|
```typescript
|
|
51
41
|
// Calculator.ts
|
|
52
42
|
export class Calculator {
|
|
53
43
|
add(a: number, b: number): number {
|
|
54
|
-
return
|
|
44
|
+
return a + b; // Minimal implementation
|
|
55
45
|
}
|
|
56
46
|
}
|
|
57
47
|
```
|
|
58
48
|
|
|
59
|
-
**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const calculator = new Calculator();
|
|
65
|
-
expect(calculator.add(10, 20)).toBe(30); // Now hardcoded 5 fails!
|
|
66
|
-
});
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Now implement properly**:
|
|
70
|
-
```typescript
|
|
71
|
-
export class Calculator {
|
|
72
|
-
add(a: number, b: number): number {
|
|
73
|
-
return a + b; // Generalized solution
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Green Phase Checklist**:
|
|
79
|
-
- [ ] All tests pass
|
|
80
|
-
- [ ] Implementation is minimal (no premature optimization)
|
|
81
|
-
- [ ] No extra features beyond what tests require
|
|
82
|
-
- [ ] Code is simple and direct
|
|
83
|
-
|
|
84
|
-
**Common Mistakes in Green Phase**:
|
|
85
|
-
- Over-engineering the solution
|
|
86
|
-
- Adding features not covered by tests
|
|
87
|
-
- Premature optimization
|
|
88
|
-
- Skipping the refactor phase
|
|
49
|
+
**GREEN Checklist**:
|
|
50
|
+
- [ ] Test passes
|
|
51
|
+
- [ ] Code is simplest possible
|
|
52
|
+
- [ ] No premature optimization
|
|
53
|
+
- [ ] No extra features
|
|
89
54
|
|
|
90
|
-
### 3.
|
|
55
|
+
### 3. REFACTOR Phase: Improve Design
|
|
91
56
|
|
|
92
|
-
**Goal**: Improve code
|
|
57
|
+
**Goal**: Improve code quality without changing behavior
|
|
93
58
|
|
|
94
59
|
```typescript
|
|
95
|
-
//
|
|
60
|
+
// Refactor: Support variable arguments
|
|
96
61
|
export class Calculator {
|
|
97
|
-
add(
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
subtract(a: number, b: number): number {
|
|
102
|
-
return a - b;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
multiply(a: number, b: number): number {
|
|
106
|
-
return a * b;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
divide(a: number, b: number): number {
|
|
110
|
-
if (b === 0) {
|
|
111
|
-
throw new Error('Division by zero');
|
|
112
|
-
}
|
|
113
|
-
return a / b;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**Refactored with better design**:
|
|
119
|
-
```typescript
|
|
120
|
-
// Extract operation interface
|
|
121
|
-
interface Operation {
|
|
122
|
-
execute(a: number, b: number): number;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
class AddOperation implements Operation {
|
|
126
|
-
execute(a: number, b: number): number {
|
|
127
|
-
return a + b;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
class DivideOperation implements Operation {
|
|
132
|
-
execute(a: number, b: number): number {
|
|
133
|
-
if (b === 0) {
|
|
134
|
-
throw new Error('Division by zero');
|
|
135
|
-
}
|
|
136
|
-
return a / b;
|
|
62
|
+
add(...numbers: number[]): number {
|
|
63
|
+
return numbers.reduce((sum, n) => sum + n, 0);
|
|
137
64
|
}
|
|
138
65
|
}
|
|
139
66
|
|
|
140
|
-
|
|
141
|
-
private operations: Map<string, Operation> = new Map([
|
|
142
|
-
['add', new AddOperation()],
|
|
143
|
-
['divide', new DivideOperation()],
|
|
144
|
-
]);
|
|
145
|
-
|
|
146
|
-
perform(operation: string, a: number, b: number): number {
|
|
147
|
-
const op = this.operations.get(operation);
|
|
148
|
-
if (!op) {
|
|
149
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
150
|
-
}
|
|
151
|
-
return op.execute(a, b);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
67
|
+
// Tests still pass!
|
|
154
68
|
```
|
|
155
69
|
|
|
156
|
-
**
|
|
70
|
+
**REFACTOR Checklist**:
|
|
157
71
|
- [ ] All tests still pass
|
|
158
72
|
- [ ] Code is more readable
|
|
159
|
-
- [ ]
|
|
160
|
-
- [ ]
|
|
161
|
-
- [ ] Better separation of concerns
|
|
162
|
-
- [ ] No new functionality added
|
|
163
|
-
|
|
164
|
-
**Refactoring Patterns**:
|
|
165
|
-
- Extract Method/Function
|
|
166
|
-
- Extract Class
|
|
167
|
-
- Introduce Parameter Object
|
|
168
|
-
- Replace Conditional with Polymorphism
|
|
169
|
-
- Extract Interface
|
|
170
|
-
- Rename for clarity
|
|
171
|
-
|
|
172
|
-
## TDD Best Practices
|
|
73
|
+
- [ ] Removed duplication
|
|
74
|
+
- [ ] Better design patterns
|
|
173
75
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
**❌ BAD: Testing implementation details**
|
|
177
|
-
```typescript
|
|
178
|
-
it('should call internal helper method', () => {
|
|
179
|
-
const spy = vi.spyOn(calculator, '_internalHelper');
|
|
180
|
-
calculator.add(2, 3);
|
|
181
|
-
expect(spy).toHaveBeenCalled(); // Brittle! Breaks on refactor
|
|
182
|
-
});
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**✅ GOOD: Testing behavior**
|
|
186
|
-
```typescript
|
|
187
|
-
it('should return sum of two numbers', () => {
|
|
188
|
-
expect(calculator.add(2, 3)).toBe(5); // Robust!
|
|
189
|
-
});
|
|
190
|
-
```
|
|
76
|
+
---
|
|
191
77
|
|
|
192
|
-
|
|
78
|
+
## TDD Benefits
|
|
193
79
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
expect(user.isActive).toBe(true);
|
|
200
|
-
expect(user.roles).toContain('admin');
|
|
201
|
-
});
|
|
202
|
-
```
|
|
80
|
+
**Design Benefits**:
|
|
81
|
+
- Forces modular, testable code
|
|
82
|
+
- Reveals design problems early
|
|
83
|
+
- Encourages SOLID principles
|
|
84
|
+
- Promotes simple solutions
|
|
203
85
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
86
|
+
**Quality Benefits**:
|
|
87
|
+
- 100% test coverage (by definition)
|
|
88
|
+
- Tests document behavior
|
|
89
|
+
- Regression safety net
|
|
90
|
+
- Faster debugging
|
|
209
91
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
92
|
+
**Productivity Benefits**:
|
|
93
|
+
- Less time debugging
|
|
94
|
+
- Confidence to refactor
|
|
95
|
+
- Faster iterations
|
|
96
|
+
- Clearer requirements
|
|
213
97
|
|
|
214
|
-
|
|
215
|
-
expect(user.isActive).toBe(true);
|
|
216
|
-
});
|
|
98
|
+
---
|
|
217
99
|
|
|
218
|
-
|
|
219
|
-
expect(user.roles).toContain('admin');
|
|
220
|
-
});
|
|
221
|
-
```
|
|
100
|
+
## BDD: Behavior-Driven Development
|
|
222
101
|
|
|
223
|
-
|
|
102
|
+
**Extension of TDD with natural language tests**
|
|
224
103
|
|
|
225
|
-
|
|
226
|
-
1. Happy path (normal input)
|
|
227
|
-
2. Edge cases (empty, null, boundary values)
|
|
228
|
-
3. Error cases (invalid input)
|
|
104
|
+
### Given-When-Then Pattern
|
|
229
105
|
|
|
230
106
|
```typescript
|
|
231
|
-
describe('
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// Edge cases
|
|
238
|
-
it('should handle division resulting in decimal', () => {
|
|
239
|
-
expect(calculator.divide(5, 2)).toBe(2.5);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should handle negative numbers', () => {
|
|
243
|
-
expect(calculator.divide(-10, 2)).toBe(-5);
|
|
244
|
-
});
|
|
107
|
+
describe('Shopping Cart', () => {
|
|
108
|
+
it('should apply 10% discount when total exceeds $100', () => {
|
|
109
|
+
// Given: A cart with $120 worth of items
|
|
110
|
+
const cart = new ShoppingCart();
|
|
111
|
+
cart.addItem({ price: 120, quantity: 1 });
|
|
245
112
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
});
|
|
113
|
+
// When: Getting the total
|
|
114
|
+
const total = cart.getTotal();
|
|
249
115
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
expect(() => calculator.divide(10, 0)).toThrow('Division by zero');
|
|
116
|
+
// Then: 10% discount applied
|
|
117
|
+
expect(total).toBe(108); // $120 - $12 (10%)
|
|
253
118
|
});
|
|
254
119
|
});
|
|
255
120
|
```
|
|
256
121
|
|
|
257
|
-
|
|
122
|
+
**BDD Benefits**:
|
|
123
|
+
- Tests readable by non-developers
|
|
124
|
+
- Clear business requirements
|
|
125
|
+
- Better stakeholder communication
|
|
126
|
+
- Executable specifications
|
|
258
127
|
|
|
259
|
-
|
|
128
|
+
---
|
|
260
129
|
|
|
261
|
-
|
|
262
|
-
// ✅ GOOD: Clear and descriptive
|
|
263
|
-
it('should return empty array when no users exist', () => { ... });
|
|
264
|
-
it('should throw ValidationError when email is invalid', () => { ... });
|
|
265
|
-
it('should apply discount when user has premium membership', () => { ... });
|
|
266
|
-
|
|
267
|
-
// ❌ BAD: Vague and unclear
|
|
268
|
-
it('works', () => { ... });
|
|
269
|
-
it('test user creation', () => { ... });
|
|
270
|
-
it('returns data', () => { ... });
|
|
271
|
-
```
|
|
130
|
+
## TDD Patterns
|
|
272
131
|
|
|
273
|
-
###
|
|
132
|
+
### Pattern 1: Test List
|
|
274
133
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// ASSERT: Verify the result
|
|
287
|
-
expect(total).toBe(22.5); // 25 * 0.9 = 22.5
|
|
288
|
-
});
|
|
134
|
+
Before coding, list all tests needed:
|
|
135
|
+
|
|
136
|
+
```markdown
|
|
137
|
+
Calculator Tests:
|
|
138
|
+
- [ ] add two positive numbers
|
|
139
|
+
- [ ] add negative numbers
|
|
140
|
+
- [ ] add zero
|
|
141
|
+
- [ ] add multiple numbers
|
|
142
|
+
- [ ] multiply two numbers
|
|
143
|
+
- [ ] divide two numbers
|
|
144
|
+
- [ ] divide by zero (error)
|
|
289
145
|
```
|
|
290
146
|
|
|
291
|
-
|
|
147
|
+
Work through list one by one.
|
|
292
148
|
|
|
293
|
-
|
|
294
|
-
```typescript
|
|
295
|
-
let user: User; // SHARED STATE!
|
|
149
|
+
### Pattern 2: Fake It Till You Make It
|
|
296
150
|
|
|
297
|
-
|
|
298
|
-
user = new User('John'); // Created once
|
|
299
|
-
});
|
|
151
|
+
Start with hardcoded returns, generalize later:
|
|
300
152
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
});
|
|
153
|
+
```typescript
|
|
154
|
+
// Test 1: add(2, 3) = 5
|
|
155
|
+
add(a, b) { return 5; } // Hardcoded!
|
|
305
156
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
157
|
+
// Test 2: add(5, 7) = 12
|
|
158
|
+
add(a, b) { return a + b; } // Generalized
|
|
309
159
|
```
|
|
310
160
|
|
|
311
|
-
|
|
161
|
+
### Pattern 3: Triangulation
|
|
162
|
+
|
|
163
|
+
Use multiple tests to force generalization:
|
|
164
|
+
|
|
312
165
|
```typescript
|
|
313
|
-
|
|
314
|
-
|
|
166
|
+
// Test 1
|
|
167
|
+
expect(fizzbuzz(3)).toBe('Fizz');
|
|
315
168
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
});
|
|
169
|
+
// Test 2
|
|
170
|
+
expect(fizzbuzz(5)).toBe('Buzz');
|
|
319
171
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
expect(user.name).toBe('Jane');
|
|
323
|
-
});
|
|
172
|
+
// Test 3
|
|
173
|
+
expect(fizzbuzz(15)).toBe('FizzBuzz');
|
|
324
174
|
|
|
325
|
-
|
|
326
|
-
expect(user.name).toBe('John'); // PASSES! Fresh instance
|
|
327
|
-
});
|
|
328
|
-
});
|
|
175
|
+
// Forces complete implementation
|
|
329
176
|
```
|
|
330
177
|
|
|
331
|
-
|
|
178
|
+
### Pattern 4: Test Data Builders
|
|
332
179
|
|
|
333
|
-
|
|
180
|
+
Create test helpers for complex objects:
|
|
334
181
|
|
|
335
|
-
**Step 1: RED - Write failing test**
|
|
336
182
|
```typescript
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const validator = new UserValidator();
|
|
340
|
-
expect(validator.validateEmail('user@example.com')).toBe(true);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
```
|
|
183
|
+
class UserBuilder {
|
|
184
|
+
private user = { name: 'Test', email: 'test@example.com', role: 'user' };
|
|
344
185
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
validateEmail(email: string): boolean {
|
|
349
|
-
return true; // Hardcoded! But test passes
|
|
186
|
+
withName(name: string) {
|
|
187
|
+
this.user.name = name;
|
|
188
|
+
return this;
|
|
350
189
|
}
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
190
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const validator = new UserValidator();
|
|
358
|
-
expect(validator.validateEmail('invalid')).toBe(false); // Forces real implementation
|
|
359
|
-
});
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
**Step 4: GREEN - Real implementation**
|
|
363
|
-
```typescript
|
|
364
|
-
export class UserValidator {
|
|
365
|
-
validateEmail(email: string): boolean {
|
|
366
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
191
|
+
withRole(role: string) {
|
|
192
|
+
this.user.role = role;
|
|
193
|
+
return this;
|
|
367
194
|
}
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
**Step 5: REFACTOR - Extract regex**
|
|
372
|
-
```typescript
|
|
373
|
-
export class UserValidator {
|
|
374
|
-
private static EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
375
195
|
|
|
376
|
-
|
|
377
|
-
return
|
|
196
|
+
build() {
|
|
197
|
+
return this.user;
|
|
378
198
|
}
|
|
379
199
|
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
**Step 6: Add more edge cases**
|
|
383
|
-
```typescript
|
|
384
|
-
it('should reject empty email', () => {
|
|
385
|
-
expect(validator.validateEmail('')).toBe(false);
|
|
386
|
-
});
|
|
387
200
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('should reject email without domain', () => {
|
|
393
|
-
expect(validator.validateEmail('user@')).toBe(false);
|
|
394
|
-
});
|
|
201
|
+
// Usage
|
|
202
|
+
const admin = new UserBuilder().withRole('admin').build();
|
|
395
203
|
```
|
|
396
204
|
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
export class UserValidator {
|
|
400
|
-
private static EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
401
|
-
|
|
402
|
-
validateEmail(email: string | null | undefined): boolean {
|
|
403
|
-
if (!email || typeof email !== 'string') {
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
return UserValidator.EMAIL_REGEX.test(email);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
```
|
|
205
|
+
---
|
|
410
206
|
|
|
411
|
-
|
|
207
|
+
## Refactoring with Confidence
|
|
412
208
|
|
|
413
|
-
**
|
|
414
|
-
```typescript
|
|
415
|
-
describe('ShoppingCart', () => {
|
|
416
|
-
it('should add item to cart', () => {
|
|
417
|
-
const cart = new ShoppingCart();
|
|
418
|
-
cart.addItem({ id: 1, name: 'Book', price: 20 });
|
|
209
|
+
**The TDD Safety Net**
|
|
419
210
|
|
|
420
|
-
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
```
|
|
211
|
+
### Refactoring Types
|
|
424
212
|
|
|
425
|
-
**
|
|
213
|
+
**1. Extract Method**:
|
|
426
214
|
```typescript
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
215
|
+
// Before
|
|
216
|
+
function processOrder(order) {
|
|
217
|
+
const total = order.items.reduce((sum, item) => sum + item.price, 0);
|
|
218
|
+
const tax = total * 0.1;
|
|
219
|
+
return total + tax;
|
|
431
220
|
}
|
|
432
221
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
addItem(item: CartItem): void {
|
|
437
|
-
this.items.push(item);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
getItemCount(): number {
|
|
441
|
-
return this.items.length;
|
|
442
|
-
}
|
|
222
|
+
// After (refactored with test safety)
|
|
223
|
+
function calculateTotal(items) {
|
|
224
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
443
225
|
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
**RED: Test calculating total**
|
|
447
|
-
```typescript
|
|
448
|
-
it('should calculate total price', () => {
|
|
449
|
-
const cart = new ShoppingCart();
|
|
450
|
-
cart.addItem({ id: 1, name: 'Book', price: 20 });
|
|
451
|
-
cart.addItem({ id: 2, name: 'Pen', price: 5 });
|
|
452
|
-
|
|
453
|
-
expect(cart.getTotal()).toBe(25);
|
|
454
|
-
});
|
|
455
|
-
```
|
|
456
226
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
// ... previous code ...
|
|
227
|
+
function calculateTax(total) {
|
|
228
|
+
return total * 0.1;
|
|
229
|
+
}
|
|
461
230
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
231
|
+
function processOrder(order) {
|
|
232
|
+
const total = calculateTotal(order.items);
|
|
233
|
+
const tax = calculateTax(total);
|
|
234
|
+
return total + tax;
|
|
465
235
|
}
|
|
466
236
|
```
|
|
467
237
|
|
|
468
|
-
**
|
|
238
|
+
**2. Remove Duplication**:
|
|
469
239
|
```typescript
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
expect(cart.getItemCount()).toBe(0);
|
|
240
|
+
// Tests force you to see duplication
|
|
241
|
+
it('should validate email', () => {
|
|
242
|
+
expect(validateEmail('test@example.com')).toBe(true);
|
|
243
|
+
expect(validateEmail('invalid')).toBe(false);
|
|
476
244
|
});
|
|
477
|
-
```
|
|
478
245
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
246
|
+
it('should validate phone', () => {
|
|
247
|
+
expect(validatePhone('+1-555-0100')).toBe(true);
|
|
248
|
+
expect(validatePhone('invalid')).toBe(false);
|
|
249
|
+
});
|
|
483
250
|
|
|
484
|
-
|
|
485
|
-
this.items = this.items.filter(item => item.id !== itemId);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
251
|
+
// Extract common validation pattern
|
|
488
252
|
```
|
|
489
253
|
|
|
490
|
-
|
|
491
|
-
```typescript
|
|
492
|
-
export class ShoppingCart {
|
|
493
|
-
private items: CartItem[] = [];
|
|
494
|
-
|
|
495
|
-
addItem(item: CartItem): void {
|
|
496
|
-
this.items.push(item);
|
|
497
|
-
}
|
|
254
|
+
### Refactoring Workflow
|
|
498
255
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
getTotal(): number {
|
|
508
|
-
return this.calculateTotal(this.items);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
private calculateTotal(items: CartItem[]): number {
|
|
512
|
-
return items.reduce((sum, item) => sum + item.price, 0);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
isEmpty(): boolean {
|
|
516
|
-
return this.items.length === 0;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
256
|
+
```
|
|
257
|
+
1. All tests GREEN? → Continue
|
|
258
|
+
2. Identify code smell
|
|
259
|
+
3. Make small refactoring
|
|
260
|
+
4. Run tests → GREEN? → Continue
|
|
261
|
+
5. Repeat until satisfied
|
|
262
|
+
6. Commit
|
|
519
263
|
```
|
|
520
264
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
### Types of Test Doubles
|
|
265
|
+
---
|
|
524
266
|
|
|
525
|
-
|
|
526
|
-
2. **Stub**: Returns predefined values
|
|
527
|
-
3. **Spy**: Records information about calls
|
|
528
|
-
4. **Mock**: Verifies interactions
|
|
529
|
-
5. **Fake**: Working implementation (simplified)
|
|
267
|
+
## TDD Anti-Patterns
|
|
530
268
|
|
|
531
|
-
###
|
|
269
|
+
### ❌ Testing Implementation Details
|
|
532
270
|
|
|
533
|
-
**RED: Test user creation with email service**
|
|
534
271
|
```typescript
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
await userService.createUser({ name: 'John', email: 'john@example.com' });
|
|
541
|
-
|
|
542
|
-
expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('john@example.com');
|
|
543
|
-
});
|
|
272
|
+
// BAD: Testing private method
|
|
273
|
+
it('should call _validateEmail internally', () => {
|
|
274
|
+
spyOn(service, '_validateEmail');
|
|
275
|
+
service.createUser({ email: 'test@example.com' });
|
|
276
|
+
expect(service._validateEmail).toHaveBeenCalled();
|
|
544
277
|
});
|
|
545
|
-
```
|
|
546
278
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
export class UserService {
|
|
554
|
-
constructor(private emailService: EmailService) {}
|
|
555
|
-
|
|
556
|
-
async createUser(userData: { name: string; email: string }): Promise<User> {
|
|
557
|
-
const user = new User(userData);
|
|
558
|
-
await this.emailService.sendWelcomeEmail(user.email);
|
|
559
|
-
return user;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Test helper
|
|
564
|
-
function createMockEmailService(): EmailService {
|
|
565
|
-
return {
|
|
566
|
-
sendWelcomeEmail: vi.fn().mockResolvedValue(undefined),
|
|
567
|
-
};
|
|
568
|
-
}
|
|
279
|
+
// GOOD: Testing behavior
|
|
280
|
+
it('should reject invalid email', () => {
|
|
281
|
+
expect(() => service.createUser({ email: 'invalid' }))
|
|
282
|
+
.toThrow('Invalid email');
|
|
283
|
+
});
|
|
569
284
|
```
|
|
570
285
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
TDD naturally leads to SOLID design:
|
|
574
|
-
|
|
575
|
-
### 1. Single Responsibility Principle (SRP)
|
|
576
|
-
|
|
577
|
-
**TDD forces SRP** because classes with multiple responsibilities are hard to test.
|
|
286
|
+
### ❌ Writing Tests After Code
|
|
578
287
|
|
|
579
288
|
```typescript
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
sendEmail() { /* ... */ }
|
|
584
|
-
saveToDatabase() { /* ... */ }
|
|
585
|
-
validateInput() { /* ... */ }
|
|
586
|
-
}
|
|
289
|
+
// Wrong order!
|
|
290
|
+
1. Write implementation
|
|
291
|
+
2. Write tests
|
|
587
292
|
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
class EmailSender {
|
|
594
|
-
sendEmail() { /* ... */ }
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
class UserRepository {
|
|
598
|
-
save() { /* ... */ }
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
class UserValidator {
|
|
602
|
-
validate() { /* ... */ }
|
|
603
|
-
}
|
|
293
|
+
// Correct TDD:
|
|
294
|
+
1. Write test (RED)
|
|
295
|
+
2. Write implementation (GREEN)
|
|
296
|
+
3. Refactor
|
|
604
297
|
```
|
|
605
298
|
|
|
606
|
-
###
|
|
607
|
-
|
|
608
|
-
**TDD encourages extension through abstraction**
|
|
299
|
+
### ❌ Large Tests
|
|
609
300
|
|
|
610
301
|
```typescript
|
|
611
|
-
//
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// Stripe implementation
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
class PayPalProcessor implements PaymentProcessor {
|
|
623
|
-
async process(amount: number): Promise<PaymentResult> {
|
|
624
|
-
// PayPal implementation
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Tests can easily mock PaymentProcessor
|
|
629
|
-
describe('OrderService', () => {
|
|
630
|
-
it('should process payment', async () => {
|
|
631
|
-
const mockProcessor: PaymentProcessor = {
|
|
632
|
-
process: vi.fn().mockResolvedValue({ success: true }),
|
|
633
|
-
};
|
|
302
|
+
// BAD: Testing multiple behaviors
|
|
303
|
+
it('should handle user lifecycle', () => {
|
|
304
|
+
const user = createUser();
|
|
305
|
+
updateUser(user, { name: 'New Name' });
|
|
306
|
+
deleteUser(user);
|
|
307
|
+
// Too much in one test!
|
|
308
|
+
});
|
|
634
309
|
|
|
635
|
-
|
|
636
|
-
|
|
310
|
+
// GOOD: One behavior per test
|
|
311
|
+
it('should create user', () => {
|
|
312
|
+
const user = createUser();
|
|
313
|
+
expect(user).toBeDefined();
|
|
314
|
+
});
|
|
637
315
|
|
|
638
|
-
|
|
639
|
-
|
|
316
|
+
it('should update user name', () => {
|
|
317
|
+
const user = createUser();
|
|
318
|
+
updateUser(user, { name: 'New Name' });
|
|
319
|
+
expect(user.name).toBe('New Name');
|
|
640
320
|
});
|
|
641
321
|
```
|
|
642
322
|
|
|
643
|
-
###
|
|
644
|
-
|
|
645
|
-
**TDD ensures LSP through contract testing**
|
|
323
|
+
### ❌ Skipping Refactor Phase
|
|
646
324
|
|
|
647
325
|
```typescript
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
// Implementations must satisfy contract
|
|
654
|
-
class Rectangle extends Shape {
|
|
655
|
-
constructor(private width: number, private height: number) {
|
|
656
|
-
super();
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
area(): number {
|
|
660
|
-
return this.width * this.height;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
class Circle extends Shape {
|
|
665
|
-
constructor(private radius: number) {
|
|
666
|
-
super();
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
area(): number {
|
|
670
|
-
return Math.PI * this.radius ** 2;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Test contract for all shapes
|
|
675
|
-
function testShapeContract(createShape: () => Shape) {
|
|
676
|
-
it('should have positive area', () => {
|
|
677
|
-
const shape = createShape();
|
|
678
|
-
expect(shape.area()).toBeGreaterThan(0);
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it('should return number', () => {
|
|
682
|
-
const shape = createShape();
|
|
683
|
-
expect(typeof shape.area()).toBe('number');
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
describe('Rectangle', () => {
|
|
688
|
-
testShapeContract(() => new Rectangle(10, 5));
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
describe('Circle', () => {
|
|
692
|
-
testShapeContract(() => new Circle(5));
|
|
693
|
-
});
|
|
326
|
+
// Don't skip refactoring!
|
|
327
|
+
RED → GREEN → REFACTOR → RED → GREEN → REFACTOR
|
|
328
|
+
↑________________↑
|
|
329
|
+
Always refactor!
|
|
694
330
|
```
|
|
695
331
|
|
|
696
|
-
|
|
332
|
+
---
|
|
697
333
|
|
|
698
|
-
|
|
334
|
+
## Mock-Driven TDD
|
|
699
335
|
|
|
700
|
-
|
|
701
|
-
// ❌ BAD: Fat interface (forces unnecessary mocking)
|
|
702
|
-
interface Worker {
|
|
703
|
-
work(): void;
|
|
704
|
-
eat(): void;
|
|
705
|
-
sleep(): void;
|
|
706
|
-
}
|
|
336
|
+
**When testing with external dependencies**
|
|
707
337
|
|
|
708
|
-
|
|
709
|
-
const mockWorker: Worker = {
|
|
710
|
-
work: vi.fn(),
|
|
711
|
-
eat: vi.fn(), // Not needed for this test!
|
|
712
|
-
sleep: vi.fn(), // Not needed for this test!
|
|
713
|
-
};
|
|
338
|
+
### Strategy 1: Dependency Injection
|
|
714
339
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
340
|
+
```typescript
|
|
341
|
+
class UserService {
|
|
342
|
+
constructor(private db: Database) {} // Inject dependency
|
|
719
343
|
|
|
720
|
-
|
|
721
|
-
|
|
344
|
+
async getUser(id: string) {
|
|
345
|
+
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
|
|
346
|
+
}
|
|
722
347
|
}
|
|
723
348
|
|
|
724
|
-
//
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
};
|
|
349
|
+
// Test with mock
|
|
350
|
+
const mockDb = { query: vi.fn().mockResolvedValue({ id: '123' }) };
|
|
351
|
+
const service = new UserService(mockDb);
|
|
728
352
|
```
|
|
729
353
|
|
|
730
|
-
###
|
|
731
|
-
|
|
732
|
-
**TDD requires dependency injection (essential for testing)**
|
|
354
|
+
### Strategy 2: Interface-Based Mocking
|
|
733
355
|
|
|
734
356
|
```typescript
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
log(message: string): void;
|
|
357
|
+
interface EmailService {
|
|
358
|
+
send(to: string, subject: string, body: string): Promise<void>;
|
|
738
359
|
}
|
|
739
360
|
|
|
740
|
-
class
|
|
741
|
-
|
|
361
|
+
class MockEmailService implements EmailService {
|
|
362
|
+
sent: any[] = [];
|
|
742
363
|
|
|
743
|
-
|
|
744
|
-
this.
|
|
745
|
-
// ...
|
|
364
|
+
async send(to: string, subject: string, body: string) {
|
|
365
|
+
this.sent.push({ to, subject, body });
|
|
746
366
|
}
|
|
747
367
|
}
|
|
748
368
|
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
service.createUser('John');
|
|
756
|
-
|
|
757
|
-
expect(mockLogger.log).toHaveBeenCalledWith('Creating user: John');
|
|
758
|
-
});
|
|
759
|
-
});
|
|
369
|
+
// Test with mock
|
|
370
|
+
const mockEmail = new MockEmailService();
|
|
371
|
+
const service = new UserService(mockEmail);
|
|
372
|
+
await service.registerUser({ email: 'test@example.com' });
|
|
373
|
+
expect(mockEmail.sent).toHaveLength(1);
|
|
760
374
|
```
|
|
761
375
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
### 1. The Liar (Passing test that doesn't test anything)
|
|
765
|
-
|
|
766
|
-
**❌ BAD**:
|
|
767
|
-
```typescript
|
|
768
|
-
it('should validate user', () => {
|
|
769
|
-
const result = true; // Hardcoded!
|
|
770
|
-
expect(result).toBe(true); // Always passes
|
|
771
|
-
});
|
|
772
|
-
```
|
|
376
|
+
---
|
|
773
377
|
|
|
774
|
-
|
|
378
|
+
## SOLID Principles Through TDD
|
|
775
379
|
|
|
776
|
-
|
|
777
|
-
```typescript
|
|
778
|
-
beforeEach(() => {
|
|
779
|
-
// 50 lines of setup code...
|
|
780
|
-
database.connect();
|
|
781
|
-
seedTestData();
|
|
782
|
-
setupMocks();
|
|
783
|
-
configureEnvironment();
|
|
784
|
-
// ...
|
|
785
|
-
});
|
|
786
|
-
```
|
|
380
|
+
**TDD naturally leads to SOLID design**
|
|
787
381
|
|
|
788
|
-
|
|
382
|
+
### Single Responsibility (SRP)
|
|
383
|
+
Tests reveal when class does too much:
|
|
789
384
|
```typescript
|
|
790
|
-
|
|
791
|
-
|
|
385
|
+
// Many tests for one class? Split it!
|
|
386
|
+
describe('UserManager', () => {
|
|
387
|
+
// 20+ tests here → Too many responsibilities
|
|
792
388
|
});
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
### 3. The Giant (One test testing everything)
|
|
796
389
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
});
|
|
390
|
+
// Refactor to multiple classes
|
|
391
|
+
describe('UserCreator', () => { /* 5 tests */ });
|
|
392
|
+
describe('UserValidator', () => { /* 5 tests */ });
|
|
393
|
+
describe('UserNotifier', () => { /* 5 tests */ });
|
|
802
394
|
```
|
|
803
395
|
|
|
804
|
-
|
|
396
|
+
### Open/Closed (OCP)
|
|
397
|
+
Tests enable extension without modification:
|
|
805
398
|
```typescript
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
### 4. The Mockery (Over-mocking)
|
|
812
|
-
|
|
813
|
-
**❌ BAD**:
|
|
814
|
-
```typescript
|
|
815
|
-
vi.mock('./utils');
|
|
816
|
-
vi.mock('./helpers');
|
|
817
|
-
vi.mock('./validators');
|
|
818
|
-
vi.mock('./formatters');
|
|
819
|
-
// Testing nothing but mocks!
|
|
820
|
-
```
|
|
399
|
+
// Testable, extensible design
|
|
400
|
+
interface PaymentProcessor {
|
|
401
|
+
process(amount: number): Promise<void>;
|
|
402
|
+
}
|
|
821
403
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
vi.mock('./externalApi'); // Mock external API only
|
|
825
|
-
// Test real code integration
|
|
404
|
+
class StripeProcessor implements PaymentProcessor { }
|
|
405
|
+
class PayPalProcessor implements PaymentProcessor { }
|
|
826
406
|
```
|
|
827
407
|
|
|
828
|
-
###
|
|
829
|
-
|
|
830
|
-
**❌ BAD**:
|
|
408
|
+
### Dependency Inversion (DIP)
|
|
409
|
+
TDD requires dependency injection:
|
|
831
410
|
```typescript
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
```
|
|
411
|
+
// Testable: Depends on abstraction
|
|
412
|
+
class OrderService {
|
|
413
|
+
constructor(private payment: PaymentProcessor) {}
|
|
414
|
+
}
|
|
837
415
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
vi.useFakeTimers();
|
|
842
|
-
// ...
|
|
843
|
-
vi.advanceTimersByTime(5000);
|
|
844
|
-
vi.restoreAllTimers();
|
|
845
|
-
});
|
|
416
|
+
// Easy to test with mocks
|
|
417
|
+
const mockPayment = new MockPaymentProcessor();
|
|
418
|
+
const service = new OrderService(mockPayment);
|
|
846
419
|
```
|
|
847
420
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
**Good TDD indicators**:
|
|
851
|
-
- ✅ 80%+ code coverage
|
|
852
|
-
- ✅ Tests run in < 1 second (unit tests)
|
|
853
|
-
- ✅ Tests are independent and can run in any order
|
|
854
|
-
- ✅ Tests are readable and self-documenting
|
|
855
|
-
- ✅ Red-Green-Refactor cycle followed consistently
|
|
856
|
-
- ✅ Code is modular and testable
|
|
857
|
-
|
|
858
|
-
**Warning signs**:
|
|
859
|
-
- ❌ Tests take minutes to run
|
|
860
|
-
- ❌ Tests fail randomly (flakiness)
|
|
861
|
-
- ❌ Hard to write tests (indicates design issues)
|
|
862
|
-
- ❌ Mocking everything (over-mocking)
|
|
863
|
-
- ❌ Tests break on refactoring (testing implementation)
|
|
864
|
-
|
|
865
|
-
## TDD in Practice: Real-World Tips
|
|
866
|
-
|
|
867
|
-
### 1. Start Small
|
|
868
|
-
Begin TDD on new features, not legacy code. Retrofit tests gradually.
|
|
869
|
-
|
|
870
|
-
### 2. Write Test First (Always!)
|
|
871
|
-
Resist urge to code first. The test is your design tool.
|
|
872
|
-
|
|
873
|
-
### 3. Keep Tests Fast
|
|
874
|
-
Unit tests should run in milliseconds. Slow tests kill TDD.
|
|
875
|
-
|
|
876
|
-
### 4. Commit Test + Code Together
|
|
877
|
-
Tests are part of the implementation, not afterthought.
|
|
878
|
-
|
|
879
|
-
### 5. Refactor Relentlessly
|
|
880
|
-
Green doesn't mean done. Refactor for clarity.
|
|
881
|
-
|
|
882
|
-
### 6. Test Behavior, Not Implementation
|
|
883
|
-
Tests should survive refactoring.
|
|
884
|
-
|
|
885
|
-
### 7. Use TDD to Drive Design
|
|
886
|
-
Let tests guide your architecture (dependency injection, SOLID).
|
|
887
|
-
|
|
888
|
-
## TDD Workflow Commands
|
|
889
|
-
|
|
890
|
-
**Development cycle**:
|
|
891
|
-
```bash
|
|
892
|
-
# 1. Write failing test (RED)
|
|
893
|
-
npm test -- --watch
|
|
894
|
-
|
|
895
|
-
# 2. Implement (GREEN)
|
|
896
|
-
# Make changes, watch tests turn green
|
|
421
|
+
---
|
|
897
422
|
|
|
898
|
-
|
|
899
|
-
# Improve code, tests stay green
|
|
423
|
+
## Quick Reference
|
|
900
424
|
|
|
901
|
-
|
|
902
|
-
git add .
|
|
903
|
-
git commit -m "feat: add user validation"
|
|
425
|
+
### TDD Workflow
|
|
904
426
|
```
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
# Ensure 80%+ coverage
|
|
427
|
+
1. Write test (RED) → Fails ✅
|
|
428
|
+
2. Minimal code (GREEN) → Passes ✅
|
|
429
|
+
3. Refactor → Still passes ✅
|
|
430
|
+
4. Repeat
|
|
910
431
|
```
|
|
911
432
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
-
|
|
915
|
-
-
|
|
916
|
-
-
|
|
917
|
-
-
|
|
433
|
+
### Test Smells
|
|
434
|
+
- Test too long (>20 lines)
|
|
435
|
+
- Multiple assertions (>3)
|
|
436
|
+
- Testing implementation
|
|
437
|
+
- Unclear test name
|
|
438
|
+
- Slow tests (>100ms)
|
|
439
|
+
- Flaky tests
|
|
918
440
|
|
|
919
|
-
|
|
441
|
+
### When to Use TDD
|
|
442
|
+
✅ New features
|
|
443
|
+
✅ Bug fixes (add test first)
|
|
444
|
+
✅ Refactoring
|
|
445
|
+
✅ Complex logic
|
|
446
|
+
✅ Public APIs
|
|
920
447
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
3. Refactor RELENTLESSLY (refactor)
|
|
925
|
-
4. Repeat
|
|
448
|
+
❌ Throwaway prototypes
|
|
449
|
+
❌ UI layout (use E2E instead)
|
|
450
|
+
❌ Highly experimental code
|
|
926
451
|
|
|
927
|
-
|
|
928
|
-
- Better design (testable = modular)
|
|
929
|
-
- Living documentation
|
|
930
|
-
- Fearless refactoring
|
|
931
|
-
- Fast feedback loop
|
|
932
|
-
- Higher confidence
|
|
452
|
+
---
|
|
933
453
|
|
|
934
|
-
**
|
|
454
|
+
**This skill is self-contained and works in ANY user project.**
|