382 lines
9.8 KiB
Markdown
382 lines
9.8 KiB
Markdown
<!-- Powered by BMAD™ Core -->
|
|
|
|
# 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
|
|
|
|
```yaml
|
|
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**
|
|
|
|
```javascript
|
|
// 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**
|
|
|
|
```javascript
|
|
// 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)
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// ✅ 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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)
|
|
|
|
```javascript
|
|
// 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!
|