Test-Driven Development
TDD is critical for reducing token consumption and preventing bugs in AI-assisted development.
Why TDD Matters for AI Development
When working with AI assistants, Test-Driven Development becomes even more critical:
TDD Benefits with AI
- • Reduces token waste - Tests define behavior upfront, AI doesn't guess
- • Catches AI mistakes early - Tests fail immediately if AI breaks something
- • Provides clear acceptance criteria - AI knows exactly what "done" looks like
- • Prevents regressions - AI can refactor confidently with test safety net
The RED-GREEN-REFACTOR Cycle
ÆtherLight enforces a mandatory 3-phase TDD cycle for all code development:
RED Phase: Write Tests First
Write tests that define the expected behavior. Tests should fail because the implementation doesn't exist yet.
// TDD RED: Write test first - it will FAIL
describe('getUserById', () => {
it('should return user when found', () => {
const user = getUserById('123');
expect(user.id).toBe('123');
});
it('should return null when not found', () => {
const user = getUserById('nonexistent');
expect(user).toBeNull();
});
});
// Run tests → FAIL (getUserById doesn't exist yet) ✓GREEN Phase: Make Tests Pass
Write the minimum code necessary to make tests pass. Don't optimize yet - just make it work.
// TDD GREEN: Implement to make tests pass
function getUserById(id: string): User | null {
const user = db.users.find(u => u.id === id);
return user || null;
}
// Run tests → PASS ✓REFACTOR Phase: Optimize
Now improve the code - add caching, optimize performance, clean up. Tests ensure you don't break anything.
// TDD REFACTOR: Optimize with confidence
const userCache = new Map<string, User>();
function getUserById(id: string): User | null {
// Check cache first
if (userCache.has(id)) {
return userCache.get(id)!;
}
const user = db.users.find(u => u.id === id);
if (user) {
userCache.set(id, user);
}
return user || null;
}
// Run tests → STILL PASS ✓ (behavior unchanged)When to Use TDD
Always Use TDD For:
- • New feature implementation
- • Bug fixes (write test reproducing bug first)
- • Refactoring existing code
- • API endpoint development
- • Business logic changes
Can Skip TDD For:
- • Typo fixes in comments
- • Documentation updates
- • Config file changes
- • Pure cosmetic changes
TDD for Bug Fixes
When fixing bugs, always write a test that reproduces the bug first:
// Bug: ConfidenceScorer returns NaN for empty task
// Step 1: Write test that reproduces bug (RED)
it('should return 0 for empty task', () => {
expect(scoreTask({})).toBe(0.0); // Currently returns NaN
});
// Step 2: Run test - it FAILS (confirms bug exists)
// Step 3: Fix the bug (GREEN)
function scoreTask(task: Task): number {
const score = calculateScore(task);
return isNaN(score) ? 0.0 : score; // Handle NaN case
}
// Step 4: Run test - it PASSES (bug is fixed)
// Step 5: This test now prevents regression foreverThis ensures the bug can never come back without tests catching it.
Coverage Requirements
ÆtherLight enforces minimum test coverage:
| Component Type | Minimum Coverage | Target Coverage |
|---|---|---|
| Core business logic | 85% | 90%+ |
| Infrastructure services | 90% | 95% |
| API endpoints | 85% | 90% |
| Utility functions | 80% | 90% |
How TDD Saves Tokens
Token Savings from TDD
- 1.Clear acceptance criteria - AI doesn't waste tokens guessing what "done" means
- 2.Immediate feedback - Tests catch mistakes instantly, no debugging conversations
- 3.Safe refactoring - AI can optimize without lengthy "did I break anything?" discussions
- 4.Regression prevention - No tokens spent fixing bugs that tests would have caught
Communicating TDD to AI
When working with AI assistants, reference TDD explicitly:
"Implement the getUserById function using TDD: 1. First write tests for: found user, not found, invalid ID 2. Then implement to make tests pass 3. Then refactor for performance (add caching)" "Fix this bug using TDD - write a failing test first that reproduces the NaN issue, then fix it" "Refactor this function - existing tests must still pass"
This gives AI clear phases to follow and prevents jumping straight to implementation.