From tdd-workflows
Implement minimal code to make failing tests pass in TDD green phase:
npx claudepluginhub EngineerWithAI/engineerwith-agents --plugin tdd-workflowsImplement minimal code to make failing tests pass in TDD green phase: [Extended thinking: This tool uses the test-automator agent to implement the minimal code necessary to make tests pass. It focuses on simplicity, avoiding over-engineering while ensuring all tests become green.] ## Implementation Process Use Task tool with subagent_type="unit-testing::test-automator" to implement minimal passing code. Prompt: "Implement MINIMAL code to make these failing tests pass: $ARGUMENTS. Follow TDD green phase principles: 1. **Pre-Implementation Analysis** - Review all failing tests and the...
/tdd-greenExecutes TDD Green phase for [requirement_name] [TASK-ID]: implements minimal code to pass Red tests, runs selective tests (Jest/pytest), documents results in phase/memo files, updates TODO to completed.
/tdd-greenImplements minimal code to pass failing tests in TDD green phase. Provide descriptions of failing tests or test file paths as argument.
/tdd-greenImplements minimal code to pass failing tests in TDD green phase. Provide descriptions of failing tests or test file paths as argument.
Implement minimal code to make failing tests pass in TDD green phase:
[Extended thinking: This tool uses the test-automator agent to implement the minimal code necessary to make tests pass. It focuses on simplicity, avoiding over-engineering while ensuring all tests become green.]
Use Task tool with subagent_type="unit-testing::test-automator" to implement minimal passing code.
Prompt: "Implement MINIMAL code to make these failing tests pass: $ARGUMENTS. Follow TDD green phase principles:
Pre-Implementation Analysis
Implementation Strategy
Code Structure Guidelines
Language-Specific Patterns
Progressive Implementation
Common Green Phase Techniques
Success Criteria ✓ All tests pass (green) ✓ No extra functionality beyond test requirements ✓ Code is readable even if not optimal ✓ No broken existing functionality ✓ Implementation time is minimized ✓ Clear path to refactoring identified
Anti-Patterns to Avoid
Implementation Metrics
Validation Steps
Output should include:
After implementation:
If tests still fail:
Test Requirements:
describe('UserService', () => {
it('should create a new user', async () => {
const user = await userService.create({ email: 'test@example.com', name: 'Test' });
expect(user.id).toBeDefined();
expect(user.email).toBe('test@example.com');
});
it('should find user by email', async () => {
await userService.create({ email: 'test@example.com', name: 'Test' });
const user = await userService.findByEmail('test@example.com');
expect(user).toBeDefined();
});
});
Stage 1: Fake It (Minimal)
class UserService {
create(data: { email: string; name: string }) {
return { id: '123', email: data.email, name: data.name };
}
findByEmail(email: string) {
return { id: '123', email: email, name: 'Test' };
}
}
Tests pass. Implementation is obviously fake but validates test structure.
Stage 2: Simple Real Implementation
class UserService {
private users: Map<string, User> = new Map();
private nextId = 1;
create(data: { email: string; name: string }) {
const user = { id: String(this.nextId++), ...data };
this.users.set(user.email, user);
return user;
}
findByEmail(email: string) {
return this.users.get(email) || null;
}
}
In-memory storage. Tests pass. Good enough for green phase.
Stage 3: Production-Ready (Refactor Phase)
class UserService {
constructor(private db: Database) {}
async create(data: { email: string; name: string }) {
const existing = await this.db.query('SELECT * FROM users WHERE email = ?', [data.email]);
if (existing) throw new Error('User exists');
const id = await this.db.insert('users', data);
return { id, ...data };
}
async findByEmail(email: string) {
return this.db.queryOne('SELECT * FROM users WHERE email = ?', [email]);
}
}
Database integration, error handling, validation - saved for refactor phase.
Test Requirements:
describe('POST /api/tasks', () => {
it('should create task and return 201', async () => {
const res = await request(app)
.post('/api/tasks')
.send({ title: 'Test Task' });
expect(res.status).toBe(201);
expect(res.body.id).toBeDefined();
expect(res.body.title).toBe('Test Task');
});
});
Stage 1: Hardcoded Response
app.post('/api/tasks', (req, res) => {
res.status(201).json({ id: '1', title: req.body.title });
});
Tests pass immediately. No logic needed yet.
Stage 2: Simple Logic
let tasks = [];
let nextId = 1;
app.post('/api/tasks', (req, res) => {
const task = { id: String(nextId++), title: req.body.title };
tasks.push(task);
res.status(201).json(task);
});
Minimal state management. Ready for more tests.
Stage 3: Layered Architecture (Refactor)
// Controller
app.post('/api/tasks', async (req, res) => {
try {
const task = await taskService.create(req.body);
res.status(201).json(task);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Service layer
class TaskService {
constructor(private repository: TaskRepository) {}
async create(data: CreateTaskDto): Promise<Task> {
this.validate(data);
return this.repository.save(data);
}
}
Proper separation of concerns added during refactor phase.
Test Requirements:
def test_product_creation():
product = Product.objects.create(name="Widget", price=9.99)
assert product.id is not None
assert product.name == "Widget"
def test_product_price_validation():
with pytest.raises(ValidationError):
Product.objects.create(name="Widget", price=-1)
Stage 1: Model Only
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
First test passes. Second test fails - validation not implemented.
Stage 2: Add Validation
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
def clean(self):
if self.price < 0:
raise ValidationError("Price cannot be negative")
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
All tests pass. Minimal validation logic added.
Stage 3: Rich Domain Model (Refactor)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
indexes = [models.Index(fields=['category', '-created_at'])]
def clean(self):
if self.price < 0:
raise ValidationError("Price cannot be negative")
if self.price > 10000:
raise ValidationError("Price exceeds maximum")
def apply_discount(self, percentage: float) -> Decimal:
return self.price * (1 - percentage / 100)
Additional features, indexes, business logic added when needed.
Test Requirements:
describe('UserProfile', () => {
it('should display user name', () => {
render(<UserProfile user={{ name: 'John', email: 'john@test.com' }} />);
expect(screen.getByText('John')).toBeInTheDocument();
});
it('should display email', () => {
render(<UserProfile user={{ name: 'John', email: 'john@test.com' }} />);
expect(screen.getByText('john@test.com')).toBeInTheDocument();
});
});
Stage 1: Minimal JSX
interface UserProfileProps {
user: { name: string; email: string };
}
const UserProfile: React.FC<UserProfileProps> = ({ user }) => (
<div>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
);
Tests pass. No styling, no structure.
Stage 2: Basic Structure
const UserProfile: React.FC<UserProfileProps> = ({ user }) => (
<div className="user-profile">
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
Added semantic HTML, className for styling hook.
Stage 3: Production Component (Refactor)
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
const [isEditing, setIsEditing] = useState(false);
return (
<div className="user-profile" role="article" aria-label="User profile">
<header>
<h2>{user.name}</h2>
<button onClick={() => setIsEditing(true)} aria-label="Edit profile">
Edit
</button>
</header>
<section>
<p>{user.email}</p>
{user.bio && <p>{user.bio}</p>}
</section>
</div>
);
};
Accessibility, interaction, additional features added incrementally.
When to Fake It:
When to Go Real:
Decision Matrix:
Complexity Low | High
↓ | ↓
Simple → REAL | FAKE first, real later
Complex → REAL | FAKE, evaluate alternatives
Simplicity Score Calculation:
Score = (Lines of Code) + (Cyclomatic Complexity × 2) + (Dependencies × 3)
< 20 → Simple enough, implement directly
20-50 → Consider simpler alternative
> 50 → Defer complexity to refactor phase
Example Evaluation:
// Option A: Direct implementation (Score: 45)
function calculateShipping(weight: number, distance: number, express: boolean): number {
let base = weight * 0.5 + distance * 0.1;
if (express) base *= 2;
if (weight > 50) base += 10;
if (distance > 1000) base += 20;
return base;
}
// Option B: Simplest for green phase (Score: 15)
function calculateShipping(weight: number, distance: number, express: boolean): number {
return express ? 50 : 25; // Fake it until more tests drive real logic
}
Choose Option B for green phase, evolve to Option A as tests require.
Green Phase: Focus on Correctness
❌ Avoid:
- Caching strategies
- Database query optimization
- Algorithmic complexity improvements
- Premature memory optimization
✓ Accept:
- O(n²) if it makes code simpler
- Multiple database queries
- Synchronous operations
- Inefficient but clear algorithms
When Performance Matters in Green Phase:
Performance Testing Integration:
// Add performance test AFTER functional tests pass
describe('Performance', () => {
it('should handle 1000 users within 100ms', () => {
const start = Date.now();
for (let i = 0; i < 1000; i++) {
userService.create({ email: `user${i}@test.com`, name: `User ${i}` });
}
expect(Date.now() - start).toBeLessThan(100);
});
});
Simple Component → Hooks → Context:
// Green Phase: Props only
const Counter = ({ count, onIncrement }) => (
<button onClick={onIncrement}>{count}</button>
);
// Refactor: Add hooks
const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
};
// Refactor: Extract to context
const Counter = () => {
const { count, increment } = useCounter();
return <button onClick={increment}>{count}</button>;
};
Function View → Class View → Generic View:
# Green Phase: Simple function
def product_list(request):
products = Product.objects.all()
return JsonResponse({'products': list(products.values())})
# Refactor: Class-based view
class ProductListView(View):
def get(self, request):
products = Product.objects.all()
return JsonResponse({'products': list(products.values())})
# Refactor: Generic view
class ProductListView(ListView):
model = Product
context_object_name = 'products'
Inline → Middleware → Service Layer:
// Green Phase: Inline logic
app.post('/api/users', (req, res) => {
const user = { id: Date.now(), ...req.body };
users.push(user);
res.json(user);
});
// Refactor: Extract middleware
app.post('/api/users', validateUser, (req, res) => {
const user = userService.create(req.body);
res.json(user);
});
// Refactor: Full layering
app.post('/api/users',
validateUser,
asyncHandler(userController.create)
);
Keep tests green during refactoring by maintaining interface contracts:
// Original implementation (tests green)
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Refactoring: Add tax calculation (keep interface)
function calculateTotal(items: Item[]): number {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const tax = subtotal * 0.1;
return subtotal + tax;
}
// Tests still green because return type/behavior unchanged
Run old and new implementations side by side:
def process_order(order):
# Old implementation (tests depend on this)
result_old = legacy_process(order)
# New implementation (testing in parallel)
result_new = new_process(order)
# Verify they match
assert result_old == result_new, "Implementation mismatch"
return result_old # Keep tests green
class PaymentService {
processPayment(amount) {
if (config.USE_NEW_PAYMENT_PROCESSOR) {
return this.newPaymentProcessor(amount);
}
return this.legacyPaymentProcessor(amount);
}
}
Use types to guide minimal implementation:
// Types define contract
interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
}
// Green phase: In-memory implementation
class InMemoryUserRepository implements UserRepository {
private users = new Map<string, User>();
async findById(id: string) {
return this.users.get(id) || null;
}
async save(user: User) {
this.users.set(user.id, user);
}
}
// Refactor: Database implementation (same interface)
class DatabaseUserRepository implements UserRepository {
constructor(private db: Database) {}
async findById(id: string) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
async save(user: User) {
await this.db.insert('users', user);
}
}
// Define contract
const userServiceContract = {
create: {
input: { email: 'string', name: 'string' },
output: { id: 'string', email: 'string', name: 'string' }
}
};
// Green phase: Implementation matches contract
class UserService {
create(data: { email: string; name: string }) {
return { id: '123', ...data }; // Minimal but contract-compliant
}
}
// Contract test ensures compliance
describe('UserService Contract', () => {
it('should match create contract', () => {
const result = userService.create({ email: 'test@test.com', name: 'Test' });
expect(typeof result.id).toBe('string');
expect(typeof result.email).toBe('string');
expect(typeof result.name).toBe('string');
});
});
Micro-Refactoring During Green Phase:
# Test passes with this
def calculate_discount(price, customer_type):
if customer_type == 'premium':
return price * 0.8
return price
# Immediate micro-refactor (tests still green)
DISCOUNT_RATES = {
'premium': 0.8,
'standard': 1.0
}
def calculate_discount(price, customer_type):
rate = DISCOUNT_RATES.get(customer_type, 1.0)
return price * rate
Safe Refactoring Checklist:
Python Type Hints:
from typing import Optional, List
from dataclasses import dataclass
@dataclass
class User:
id: str
email: str
name: str
class UserService:
def create(self, email: str, name: str) -> User:
return User(id="123", email=email, name=name)
def find_by_email(self, email: str) -> Optional[User]:
return None # Minimal implementation
TypeScript Strict Mode:
// Enable strict mode in tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
// Implementation guided by types
interface CreateUserDto {
email: string;
name: string;
}
class UserService {
create(data: CreateUserDto): User {
// Type system enforces contract
return { id: '123', email: data.email, name: data.name };
}
}
Using Copilot/AI Tools:
AI Prompt Pattern:
Given these failing tests:
[paste tests]
Provide the MINIMAL implementation that makes tests pass.
Do not add error handling, validation, or features beyond test requirements.
Focus on simplicity over completeness.
Local → Container → Cloud:
// Green Phase: Local implementation
class CacheService {
private cache = new Map();
get(key) { return this.cache.get(key); }
set(key, value) { this.cache.set(key, value); }
}
// Refactor: Redis-compatible interface
class CacheService {
constructor(private redis) {}
async get(key) { return this.redis.get(key); }
async set(key, value) { return this.redis.set(key, value); }
}
// Production: Distributed cache with fallback
class CacheService {
constructor(private redis, private fallback) {}
async get(key) {
try {
return await this.redis.get(key);
} catch {
return this.fallback.get(key);
}
}
}
Add observability hooks during green phase:
class OrderService {
async createOrder(data: CreateOrderDto): Promise<Order> {
console.log('[OrderService] Creating order', { data }); // Simple logging
const order = { id: '123', ...data };
console.log('[OrderService] Order created', { orderId: order.id }); // Success log
return order;
}
}
// Refactor: Structured logging
class OrderService {
constructor(private logger: Logger) {}
async createOrder(data: CreateOrderDto): Promise<Order> {
this.logger.info('order.create.start', { data });
const order = await this.repository.save(data);
this.logger.info('order.create.success', {
orderId: order.id,
duration: Date.now() - start
});
return order;
}
}
Tests to make pass: $ARGUMENTS