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
┌─────────┐
│ 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
// 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
npm test # or pytest, jest, etc.⚠️ The test MUST fail - if it passes, the bug isn't reproduced correctly
-
Commit Test
git 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
// 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
npm test✅ All tests MUST pass - new test and existing tests
-
Commit Fix
git 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)
git 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
{
"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
it('should throw on invalid input', () => {
expect(() => validateEmail('')).toThrow('Email required');
});
Testing Async Code
it('should fetch user data', async () => {
const user = await userService.fetch('123');
expect(user.name).toBe('John');
});
Testing Edge Cases
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
