9.8 KiB
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
- Make it work first - Focus on making tests pass, not perfect design
- Minimal implementation - Write only what's needed for green tests
- No feature creep - Don't add functionality without failing tests
- Fast feedback - Run tests frequently during implementation
- Traceability - Link implementation directly to test requirements
Implementation Strategy
The Three Rules of TDD (Uncle Bob)
- Don't write production code unless it makes a failing test pass
- Don't write more test code than necessary to demonstrate failure (QA phase)
- 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:
- All tests consistently pass
- Implementation is obviously minimal
- Each code block addresses specific test requirements
- No functionality exists without corresponding tests
- Tests run quickly and reliably
- 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!