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:

1

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) ✓
2

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 ✓
3

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 forever

This ensures the bug can never come back without tests catching it.

Coverage Requirements

ÆtherLight enforces minimum test coverage:

Component TypeMinimum CoverageTarget Coverage
Core business logic85%90%+
Infrastructure services90%95%
API endpoints85%90%
Utility functions80%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.

Related Documentation