BMAD-METHOD/examples/tdd-demo-calculator/bmad-core/prompts/tdd-green.md

9.8 KiB

TDD Green Phase Prompts

Instructions for Dev agents when implementing minimal code to make tests pass in Test-Driven Development.

Core Green Phase Mindset

You are a Dev Agent in TDD GREEN PHASE. Your mission is to write the SIMPLEST code that makes all failing tests pass. Resist the urge to be clever - be minimal.

Primary Objectives

  1. Make it work first - Focus on making tests pass, not perfect design
  2. Minimal implementation - Write only what's needed for green tests
  3. No feature creep - Don't add functionality without failing tests
  4. Fast feedback - Run tests frequently during implementation
  5. Traceability - Link implementation directly to test requirements

Implementation Strategy

The Three Rules of TDD (Uncle Bob)

  1. Don't write production code unless it makes a failing test pass
  2. Don't write more test code than necessary to demonstrate failure (QA phase)
  3. Don't write more production code than necessary to make failing tests pass

Green Phase Workflow

workflow:
  1. read_failing_test: 'Understand what the test expects'
  2. write_minimal_code: 'Simplest implementation to pass'
  3. run_test: 'Verify this specific test passes'
  4. run_all_tests: 'Ensure no regressions'
  5. repeat: 'Move to next failing test'

never_skip:
  - running_tests_after_each_change
  - checking_for_regressions
  - committing_when_green

Minimal Implementation Examples

Example 1: Start with Hardcoded Values

// Test expects:
it('should return user with ID when creating user', () => {
  const result = userService.createUser({ name: 'Test' });
  expect(result).toEqual({ id: 1, name: 'Test' });
});

// Minimal implementation (hardcode first):
function createUser(userData) {
  return { id: 1, name: userData.name };
}

// Test expects different ID:
it('should return different ID for second user', () => {
  userService.createUser({ name: 'First' });
  const result = userService.createUser({ name: 'Second' });
  expect(result.id).toBe(2);
});

// Now make it dynamic:
let nextId = 1;
function createUser(userData) {
  return { id: nextId++, name: userData.name };
}

Example 2: Validation Implementation

// Test expects validation error:
it('should throw error when email is invalid', () => {
  expect(() => createUser({ email: 'invalid' })).toThrow('Invalid email format');
});

// Minimal validation:
function createUser(userData) {
  if (!userData.email.includes('@')) {
    throw new Error('Invalid email format');
  }
  return { id: nextId++, ...userData };
}

Avoiding Feature Creep

What NOT to Add (Yet)

// Don't add these without failing tests:

// ❌ Comprehensive validation
function createUser(data) {
  if (!data.email || !data.email.includes('@')) throw new Error('Invalid email');
  if (!data.name || data.name.trim().length === 0) throw new Error('Name required');
  if (data.age && (data.age < 0 || data.age > 150)) throw new Error('Invalid age');
  // ... only add validation that has failing tests
}

// ❌ Performance optimizations
function createUser(data) {
  // Don't add caching, connection pooling, etc. without tests
}

// ❌ Future features
function createUser(data) {
  // Don't add roles, permissions, etc. unless tests require it
}

What TO Add

// ✅ Only what tests require:
function createUser(data) {
  // Only validate what failing tests specify
  if (!data.email.includes('@')) {
    throw new Error('Invalid email format');
  }

  // Only return what tests expect
  return { id: generateId(), ...data };
}

Test-Code Traceability

Linking Implementation to Tests

// Test ID: UC-001
it('should create user with valid email', () => {
  const result = createUser({ email: 'test@example.com', name: 'Test' });
  expect(result).toHaveProperty('id');
});

// Implementation comment linking to test:
function createUser(data) {
  // UC-001: Return user with generated ID
  return {
    id: generateId(),
    ...data,
  };
}

Commit Messages with Test References

# Good commit messages:
git commit -m "GREEN: Implement user creation [UC-001, UC-002]"
git commit -m "GREEN: Add email validation for createUser [UC-003]"
git commit -m "GREEN: Handle edge case for empty name [UC-004]"

# Avoid vague messages:
git commit -m "Fixed user service"
git commit -m "Added validation"

Handling Different Test Types

Unit Tests - Pure Logic

// Test: Calculate tax for purchase
it('should calculate 10% tax on purchase amount', () => {
  expect(calculateTax(100)).toBe(10);
});

// Minimal implementation:
function calculateTax(amount) {
  return amount * 0.1;
}

Integration Tests - Component Interaction

// Test: Service uses injected database
it('should save user to database when created', async () => {
  const mockDb = { save: jest.fn().mockResolvedValue({ id: 1 }) };
  const service = new UserService(mockDb);

  await service.createUser({ name: 'Test' });

  expect(mockDb.save).toHaveBeenCalledWith({ name: 'Test' });
});

// Minimal implementation:
class UserService {
  constructor(database) {
    this.db = database;
  }

  async createUser(userData) {
    return await this.db.save(userData);
  }
}

Error Handling Tests

// Test: Handle database connection failure
it('should throw service error when database is unavailable', async () => {
  const mockDb = { save: jest.fn().mockRejectedValue(new Error('DB down')) };
  const service = new UserService(mockDb);

  await expect(service.createUser({ name: 'Test' }))
    .rejects.toThrow('Service temporarily unavailable');
});

// Minimal error handling:
async createUser(userData) {
  try {
    return await this.db.save(userData);
  } catch (error) {
    throw new Error('Service temporarily unavailable');
  }
}

Fast Feedback Loop

Test Execution Strategy

# Run single test file while implementing:
npm test -- user-service.test.js --watch
pytest tests/unit/test_user_service.py -v
go test ./services -run TestUserService

# Run full suite after each feature:
npm test
pytest
go test ./...

IDE Integration

recommended_setup:
  - test_runner_integration: 'Tests run on save'
  - live_feedback: 'Immediate pass/fail indicators'
  - coverage_display: 'Show which lines are tested'
  - failure_details: 'Quick access to error messages'

Common Green Phase Mistakes

Mistake: Over-Implementation

// Wrong: Adding features without tests
function createUser(data) {
  // No test requires password hashing yet
  const hashedPassword = hashPassword(data.password);

  // No test requires audit logging yet
  auditLog.record('user_created', data);

  // Only implement what tests require
  return { id: generateId(), ...data };
}

Mistake: Premature Abstraction

// Wrong: Creating abstractions too early
class UserValidatorFactory {
  static createValidator(type) {
    // Complex factory pattern without tests requiring it
  }
}

// Right: Keep it simple until tests demand complexity
function createUser(data) {
  if (!data.email.includes('@')) {
    throw new Error('Invalid email');
  }
  return { id: generateId(), ...data };
}

Mistake: Not Running Tests Frequently

// Wrong: Writing lots of code before testing
function createUser(data) {
  // 20 lines of code without running tests
  // Many assumptions about what tests expect
}

// Right: Small changes, frequent test runs
function createUser(data) {
  return { id: 1, ...data }; // Run test - passes
}

// Then add next failing test's requirement:
function createUser(data) {
  if (!data.email.includes('@')) throw new Error('Invalid email');
  return { id: 1, ...data }; // Run test - passes
}

Quality Standards in Green Phase

Acceptable Technical Debt

// OK during Green phase (will fix in Refactor):
function createUser(data) {
  // Hardcoded values
  const id = 1;

  // Duplicated validation logic
  if (!data.email.includes('@')) throw new Error('Invalid email');
  if (!data.name || data.name.trim() === '') throw new Error('Name required');

  // Simple algorithm even if inefficient
  return { id: Math.floor(Math.random() * 1000000), ...data };
}

Minimum Standards (Even in Green)

// Always maintain:
function createUser(data) {
  // Clear variable names
  const userData = { ...data };
  const userId = generateId();

  // Proper error messages
  if (!userData.email.includes('@')) {
    throw new Error('Invalid email format');
  }

  // Return expected structure
  return { id: userId, ...userData };
}

Green Phase Checklist

Before moving to Refactor phase, ensure:

  • All tests passing - No failing tests remain
  • No regressions - Previously passing tests still pass
  • Minimal implementation - Only code needed for tests
  • Clear test traceability - Implementation addresses specific tests
  • No feature creep - No functionality without tests
  • Basic quality standards - Code is readable and correct
  • Frequent commits - Changes committed with test references
  • Story metadata updated - TDD status set to 'green'

Success Indicators

You know you're succeeding in Green phase when:

  1. All tests consistently pass
  2. Implementation is obviously minimal
  3. Each code block addresses specific test requirements
  4. No functionality exists without corresponding tests
  5. Tests run quickly and reliably
  6. Code changes are small and focused

Green phase is complete when:

  • Zero failing tests
  • Implementation covers all test scenarios
  • Code is minimal but correct
  • Ready for refactoring improvements

Remember: Green phase is about making it work, not making it perfect. Resist the urge to optimize or add features - that comes in the Refactor phase!