TDD Workflow Skill
This skill implements the Test-Driven Development (TDD) cycle: RED → GREEN → REFACTOR.
Purpose
Ensure all fixes are implemented following TDD principles, resulting in well-tested, high-quality code.
When to Use
- •Implementing any bug fix
- •Adding new functionality
- •Modifying existing behavior
The TDD Cycle
code
┌─────────┐
│ RED │ Write a failing test
└────┬────┘
│
┌────▼────┐
│ GREEN │ Write minimal code to pass
└────┬────┘
│
┌────▼────┐
│REFACTOR │ Clean up (optional)
└────┬────┘
│
└────► Repeat if needed
Process
Phase 1: RED (Write Failing Test)
Objective: Create a test that reproduces the bug and fails.
- •
Identify Test Location
- •Find existing test file OR
- •Create new test file following project conventions
- •Examples:
*.test.ts,*.spec.js,test_*.py
- •
Write Test Case
typescript// Example: Testing null check for user session describe('AuthService', () => { it('should handle expired session gracefully', () => { const auth = new AuthService(); const expiredSession = { user: undefined }; // This should NOT throw an error expect(() => auth.getCurrentUser(expiredSession)).not.toThrow(); expect(auth.getCurrentUser(expiredSession)).toBeNull(); }); }); - •
Run Tests - Verify Failure
bashnpm test # or pytest, jest, etc.
⚠️ The test MUST fail - if it passes, the bug isn't reproduced correctly
- •
Commit Test
bashgit add <test-files> git commit -m "test: add test for [bug description]"
Phase 2: GREEN (Implement Fix)
Objective: Write the minimum code to make the test pass.
- •
Implement Minimal Fix
typescript// Before getCurrentUser(session: Session): User { return session.user.id; // Crashes on undefined } // After (minimal fix) getCurrentUser(session: Session): User | null { if (!session.user) { return null; } return session.user.id; } - •
Run Tests - Verify Passing
bashnpm test
✅ All tests MUST pass - new test and existing tests
- •
Commit Fix
bashgit add <source-files> git commit -m "fix: [description] ([issue-key])"
Phase 3: REFACTOR (Optional)
Objective: Clean up code while keeping tests green.
- •
Identify Improvements
- •Remove duplication
- •Improve naming
- •Extract methods/functions
- •Simplify logic
- •
Refactor in Small Steps
- •Make one change at a time
- •Run tests after each change
- •Ensure all tests still pass
- •
Commit Refactoring (if significant)
bashgit add <files> git commit -m "refactor: [description]"
Verification Checklist
Before completing the TDD cycle:
- • New test(s) written and passing
- • All existing tests still pass
- • Code follows project patterns
- • No linting errors
- • Commits use conventional format
Output Format
json
{
"phase": "red|green|refactor|complete",
"test_results": {
"new_tests": 2,
"total_tests": 150,
"passed": 150,
"failed": 0
},
"commits": [
{
"phase": "red",
"sha": "abc123",
"message": "test: add test for expired session handling"
},
{
"phase": "green",
"sha": "def456",
"message": "fix: add null check for user session (PROJ-123)"
}
],
"status": "success|failed",
"errors": []
}
Common Patterns
Testing for Exceptions
typescript
it('should throw on invalid input', () => {
expect(() => validateEmail('')).toThrow('Email required');
});
Testing Async Code
typescript
it('should fetch user data', async () => {
const user = await userService.fetch('123');
expect(user.name).toBe('John');
});
Testing Edge Cases
typescript
it('should handle null input', () => { /* ... */ });
it('should handle empty array', () => { /* ... */ });
it('should handle max value', () => { /* ... */ });
Error Handling
| Situation | Action |
|---|---|
| Can't find test file location | Follow project conventions or create new |
| Existing tests fail | Fix the root cause, don't skip tests |
| Tests pass on RED phase | Test isn't reproducing the bug correctly |
| Tests fail on GREEN phase | Fix is incomplete or incorrect |
Important
- •NEVER skip the RED phase - always start with a failing test
- •Keep changes minimal in GREEN phase
- •Run tests frequently - after every change
- •If stuck, ask for help - don't force incorrect code
- •Tests are documentation - make them readable