Lesson 6: TDD Workflow
Time: 45 minutes Goal: Master test-first development
Why TDD?β
TDD is Optional
TDD improves code quality but slows development. Use it when correctness matters (critical logic, APIs). Skip it for prototyping.
Test-After (Traditional)β
Write Code β Write Tests β Find Bugs β Fix β Repeat
βββ Often: No tests at all
Test-First (TDD)β
Write Test β Watch Fail β Write Code β Watch Pass β Refactor
βΌ βΌ βΌ βΌ βΌ
Define Verify Minimal Verify Improve
behavior test works implement it works quality
The Red-Green-Refactor Cycleβ
βββββββββββ
β RED β Write failing test
ββββββ¬βββββ
βΌ
βββββββββββ
β GREEN β Minimal code to pass
ββββββ¬βββββ
βΌ
βββββββββββ
βREFACTOR β Clean up, keep green
ββββββ¬βββββ
βΌ
[Next Test]
SpecWeave TDD Commandsβ
/specweave:tdd-cycle # Full orchestration
/specweave:tdd-red # Write failing tests
/specweave:tdd-green # Implement to pass
/specweave:tdd-refactor # Clean up code
Example: Calculatorβ
Let's build a calculator using TDD.
Step 1: Create Incrementβ
/specweave:increment "Calculator with basic operations"
Step 2: Start TDDβ
/specweave:tdd-cycle
Phase 1: RED (Write Failing Tests)β
// tests/calculator.test.ts
import { describe, it, expect } from 'vitest';
import { add } from '../src/calculator';
describe('add()', () => {
it('adds two positives', () => {
expect(add(2, 3)).toBe(5);
});
it('adds negatives', () => {
expect(add(-5, 3)).toBe(-2);
});
it('handles zero', () => {
expect(add(0, 0)).toBe(0);
});
});
Running tests:
FAIL tests/calculator.test.ts
β adds two positives
β adds negatives
β handles zero
5 tests failed (expected!)
β
RED PHASE COMPLETE
Phase 2: GREEN (Make Tests Pass)β
// src/calculator.ts
export function add(a: number, b: number): number {
return a + b;
}
Running tests:
PASS tests/calculator.test.ts
β adds two positives
β adds negatives
β handles zero
5 tests passed!
β
GREEN PHASE COMPLETE
Phase 3: REFACTOR (Improve Code)β
// src/calculator.ts (after refactor)
export function add(a: number, b: number): number {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Both arguments must be numbers');
}
if (Number.isNaN(a) || Number.isNaN(b)) {
throw new TypeError('Arguments cannot be NaN');
}
return a + b;
}
Verification:
PASS tests/calculator.test.ts
β All original tests passing
+ 2 new validation tests added
β
REFACTOR PHASE COMPLETE
BDD in tasks.mdβ
Every task includes BDD test plans:
### T-004: Implement divide()
**Test Plan** (BDD):
**Scenario 1: Normal division**
- **Given** two numbers 10 and 2
- **When** divide(10, 2) is called
- **Then** result is 5
**Scenario 2: Division by zero**
- **Given** divisor is 0
- **When** divide(10, 0) is called
- **Then** Error "Division by zero" is thrown
**Test Cases**:
- divide_normalDivision_returnsQuotient
- divide_byZero_throwsError
TDD Best Practicesβ
Test Behavior, Not Implementationβ
// β BAD: Testing implementation
it('uses _calculateInternal method', () => {
const spy = vi.spyOn(calc, '_calculateInternal');
calc.add(1, 2);
expect(spy).toHaveBeenCalled();
});
// β
GOOD: Testing behavior
it('adds two numbers', () => {
expect(calc.add(1, 2)).toBe(3);
});
Descriptive Test Namesβ
// β BAD
it('test1', () => { ... });
// β
GOOD
it('add_twoPositives_returnsSum', () => { ... });
One Assertion Per Testβ
// β BAD: Multiple unrelated assertions
it('calculator works', () => {
expect(calc.add(1, 2)).toBe(3);
expect(calc.subtract(5, 3)).toBe(2);
});
// β
GOOD: Focused tests
it('adds numbers', () => {
expect(calc.add(1, 2)).toBe(3);
});
it('subtracts numbers', () => {
expect(calc.subtract(5, 3)).toBe(2);
});
Try It Yourselfβ
Build a string calculator using TDD:
Requirements:
"1,2"returns3- Empty string returns
0 - Single number returns that number
- Negative numbers throw error
/specweave:increment "String calculator with TDD"
/specweave:tdd-cycle
/specweave:next
Glossary Terms Usedβ
- TDD β Test-Driven Development
- BDD β Behavior-Driven Development (Given/When/Then)
- Unit Testing β Testing individual functions
- Test Coverage β Percentage of code tested
Key Takeawaysβ
| Command | Phase | Purpose |
|---|---|---|
/specweave:tdd-cycle | All | Full orchestration |
/specweave:tdd-red | RED | Write failing tests |
/specweave:tdd-green | GREEN | Implement to pass |
/specweave:tdd-refactor | REFACTOR | Improve code |
The TDD mantra:
- Red: Write a failing test
- Green: Make it pass (minimal code)
- Refactor: Clean up (keep tests green)
What's Next?β
Connect SpecWeave to your project management tools.
:next β Lesson 7: External Tools