← Back to list

tdd-workflow
by SPeeDoA1
⭐ 2🍴 0📅 Jan 24, 2026
SKILL.md
name: tdd-workflow description: Complete Test-Driven Development workflow with red-green-refactor cycle license: MIT
TDD Workflow Skill
Use this skill when implementing features using Test-Driven Development methodology.
When to Use
- Implementing new features or functions
- Adding new API endpoints
- Creating utility functions
- Building components with complex logic
- When high test coverage is required
TDD Cycle
┌──────────────────────────────────────────────────────────┐
│ │
│ 🔴 RED → 🟢 GREEN → 🔵 REFACTOR │
│ Write test Make pass Improve code │
│ that fails (minimal) (keep green) │
│ │
│ ↺ Repeat │
└──────────────────────────────────────────────────────────┘
Step-by-Step Process
Phase 1: RED - Write Failing Test
-
Understand the requirement
- What is the input?
- What is the expected output?
- What are the edge cases?
-
Write the simplest test case first
describe('calculateDiscount', () => { it('should return 0 for orders under $50', () => { expect(calculateDiscount(30)).toBe(0); }); }); -
Run the test - it MUST fail
npm test -- calculateDiscount
Phase 2: GREEN - Make It Pass
-
Write the minimal code to pass
function calculateDiscount(orderTotal: number): number { return 0; } -
Run the test - it MUST pass
npm test -- calculateDiscount -
Don't over-engineer - Only write enough code to pass the current test
Phase 3: REFACTOR - Improve the Code
- Only refactor when tests are green
- Keep tests passing throughout
- Improve code quality:
- Extract constants
- Rename for clarity
- Remove duplication
- Simplify logic
Phase 4: Repeat
Add the next test case and repeat the cycle:
it('should return 10% for orders $50-$100', () => {
expect(calculateDiscount(75)).toBe(7.5);
});
Test Structure
Arrange-Act-Assert Pattern
it('should apply discount to premium users', () => {
// Arrange
const user = createUser({ isPremium: true });
const order = createOrder({ total: 100 });
// Act
const result = applyDiscount(user, order);
// Assert
expect(result.total).toBe(90);
expect(result.discountApplied).toBe(10);
});
Test Naming Convention
// Format: should_[expected behavior]_when_[condition]
it('should return empty array when no items match filter', () => {});
it('should throw ValidationError when email is invalid', () => {});
it('should retry 3 times when API returns 503', () => {});
Complete Example
Requirement
Create a password validator that checks:
- Minimum 8 characters
- At least one uppercase letter
- At least one number
TDD Implementation
// Step 1: RED - First test
describe('validatePassword', () => {
it('should reject passwords shorter than 8 characters', () => {
expect(validatePassword('Short1')).toEqual({
valid: false,
errors: ['Password must be at least 8 characters'],
});
});
});
// Step 2: GREEN - Minimal implementation
function validatePassword(password: string) {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
return { valid: errors.length === 0, errors };
}
// Step 3: RED - Next test
it('should reject passwords without uppercase', () => {
expect(validatePassword('lowercase1')).toEqual({
valid: false,
errors: ['Password must contain an uppercase letter'],
});
});
// Step 4: GREEN - Add uppercase check
function validatePassword(password: string) {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain an uppercase letter');
}
return { valid: errors.length === 0, errors };
}
// Step 5: RED - Test for number requirement
it('should reject passwords without numbers', () => {
expect(validatePassword('NoNumbers')).toEqual({
valid: false,
errors: ['Password must contain a number'],
});
});
// Step 6: GREEN - Add number check
function validatePassword(password: string) {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain an uppercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain a number');
}
return { valid: errors.length === 0, errors };
}
// Step 7: REFACTOR - Clean up
const PASSWORD_RULES = [
{ test: (p: string) => p.length >= 8, message: 'Password must be at least 8 characters' },
{ test: (p: string) => /[A-Z]/.test(p), message: 'Password must contain an uppercase letter' },
{ test: (p: string) => /[0-9]/.test(p), message: 'Password must contain a number' },
];
function validatePassword(password: string) {
const errors = PASSWORD_RULES
.filter(rule => !rule.test(password))
.map(rule => rule.message);
return { valid: errors.length === 0, errors };
}
// Step 8: RED - Happy path test
it('should accept valid passwords', () => {
expect(validatePassword('ValidPass1')).toEqual({
valid: true,
errors: [],
});
});
// Already passes! ✅
Best Practices
Do
- Write one test at a time
- Run tests after every change
- Test behavior, not implementation
- Keep tests independent
- Use descriptive test names
Don't
- Write tests after code
- Skip the RED phase
- Write multiple tests before implementing
- Test private methods directly
- Couple tests to implementation details
Commands
# Run tests in watch mode
npm test -- --watch
# Run specific test file
npm test -- validatePassword
# Run with coverage
npm test -- --coverage
# Run and update snapshots
npm test -- --updateSnapshot
When TDD Might Not Fit
- Exploratory/prototype code
- UI layout (visual testing better)
- Third-party integration spikes
- Performance optimization (profile first)
In these cases, write tests after, but still write them.
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon