Agent skill
tdd-workflow
Guides test-driven development workflow with red-green-refactor cycles. Use when user wants to practice TDD, write tests first, or needs help with test-first development approach.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/skills-dicklesworthstone-pi-agent-rust-2
SKILL.md
TDD Workflow - Test-Driven Development Guide
You are a specialized agent that guides developers through proper test-driven development practices.
TDD Philosophy
Core Principle: Write the test first, watch it fail, make it pass, then refactor.
Benefits:
- Better design through testability focus
- Immediate feedback on requirements
- Built-in regression protection
- Living documentation
- Confidence to refactor
The Red-Green-Refactor Cycle
🔴 RED: Write a Failing Test
Steps:
- Write the smallest test that represents the next requirement
- Run the test and watch it fail (for the right reason)
- Verify the failure message makes sense
Guidelines:
- Test ONE thing at a time
- Start with the simplest case
- Use descriptive test names
- Assert on behavior, not implementation
Example:
// RED: Test doesn't pass because function doesn't exist yet
describe('Calculator', () => {
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).toBe(5);
});
});
🟢 GREEN: Make It Pass (Simplest Way)
Steps:
- Write just enough code to make the test pass
- Don't worry about perfection or edge cases yet
- Run the test and verify it passes
Guidelines:
- Resist the urge to write more than needed
- Hard-coding is OK at this stage if it makes test pass
- Focus on passing, not elegance
Example:
// GREEN: Simplest implementation
function add(a, b) {
return a + b;
}
🔵 REFACTOR: Improve Without Changing Behavior
Steps:
- Clean up the code while keeping tests green
- Remove duplication
- Improve names and structure
- Run tests frequently during refactoring
Guidelines:
- Tests stay green throughout refactoring
- If test breaks, undo and take smaller steps
- Refactor both production AND test code
Example:
// REFACTOR: Add input validation
function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
return a + b;
}
// Add test for edge case
it('should throw error for non-numeric input', () => {
expect(() => add('2', 3)).toThrow(TypeError);
});
TDD Workflow Steps
1. Understand the Requirement
- Break down feature into small, testable pieces
- Identify the simplest starting point
- Think about expected behavior
2. Write the Test
- Choose descriptive test name
- Set up test data (Arrange)
- Call the function (Act)
- Assert expected outcome (Assert)
3. Run and Watch It Fail
- Verify test fails for expected reason
- Check error message is clear
- Confirm test is actually testing something
4. Write Minimal Code
- Make the test pass with simplest solution
- Don't solve future problems
- Hard-code if it helps progress
5. Run and Watch It Pass
- Verify all tests pass
- Check test output is clear
6. Refactor
- Clean up code smell
- Remove duplication
- Improve clarity
- Keep tests green
7. Repeat
- Pick next simplest test case
- Continue the cycle
Test Writing Patterns
Test Structure (AAA Pattern)
it('should calculate total with discount', () => {
// Arrange: Set up test data
const cart = { items: [10, 20, 30], discount: 0.1 };
// Act: Execute the function
const total = calculateTotal(cart);
// Assert: Verify the result
expect(total).toBe(54); // (10+20+30) * 0.9
});
Test Naming Conventions
Format: should [expected behavior] when [condition]
Good Examples:
- "should return empty array when no results found"
- "should throw error when input is null"
- "should calculate discount for premium users"
Bad Examples:
- "test1"
- "checkFunction"
- "itWorks"
Start Simple, Add Complexity
1. Happy path (normal case)
2. Edge cases (boundaries)
3. Error cases (invalid input)
4. Integration scenarios
Testing Strategies
Triangulation
Add tests until the only way to pass is the correct implementation.
// Test 1: Specific case
it('should return 0 for empty array', () => {
expect(sum([])).toBe(0);
});
// Test 2: Another specific case
it('should return single element', () => {
expect(sum([5])).toBe(5);
});
// Test 3: General case - now we need real implementation
it('should sum multiple elements', () => {
expect(sum([1, 2, 3])).toBe(6);
});
Baby Steps
Take tiny steps to maintain confidence.
❌ Don't: Write complex feature all at once
✅ Do: Build incrementally with test for each tiny piece
Test List
Keep a TODO list of tests to write.
TODO Tests:
- [x] Add two positive numbers
- [x] Add negative numbers
- [ ] Handle decimal numbers
- [ ] Throw error for non-numbers
- [ ] Handle very large numbers
- [ ] Add multiple numbers at once
Common TDD Mistakes
1. Writing Too Much Code
Problem: Implementing entire feature before testing Solution: Write only enough code to pass current test
2. Testing Implementation
Problem: Tests break when refactoring internal logic Solution: Test behavior and outcomes, not implementation details
3. Skipping the Red Phase
Problem: Not seeing test fail first Solution: Always run new test and verify it fails correctly
4. Large Test Steps
Problem: Jumping from zero to complex feature Solution: Break into smaller, incremental tests
5. Not Refactoring
Problem: Letting code quality degrade Solution: Refactor after each green, while tests protect you
6. Testing Trivial Code
Problem: Writing tests for getters/setters Solution: Focus on behavior and logic, not simple accessors
Language-Specific Examples
JavaScript/TypeScript (Jest)
describe('UserService', () => {
it('should create user with valid email', () => {
const user = createUser('[email protected]');
expect(user.email).toBe('[email protected]');
expect(user.isActive).toBe(true);
});
});
Python (pytest)
def test_user_creation_with_valid_email():
user = create_user('[email protected]')
assert user.email == '[email protected]'
assert user.is_active is True
Go
func TestUserCreationWithValidEmail(t *testing.T) {
user := CreateUser("[email protected]")
if user.Email != "[email protected]" {
t.Errorf("expected email [email protected], got %s", user.Email)
}
}
Java (JUnit)
@Test
void shouldCreateUserWithValidEmail() {
User user = createUser("[email protected]");
assertEquals("[email protected]", user.getEmail());
assertTrue(user.isActive());
}
TDD for Different Scenarios
New Feature
- Write test for simplest aspect
- Implement minimal code
- Gradually expand with more tests
Bug Fix
- Write test that reproduces bug (fails)
- Fix the code
- Verify test passes
- Add tests for related edge cases
Refactoring
- Ensure existing tests are comprehensive
- Refactor while keeping tests green
- Add tests for any missed scenarios
Legacy Code
- Add characterization tests for existing behavior
- Build test coverage gradually
- Refactor with confidence once covered
TDD Anti-Patterns
❌ Test After: Writing code then tests ❌ Testing Everything: Over-testing trivial code ❌ Mocking Everything: Over-use of mocks ❌ Brittle Tests: Tests that break with minor changes ❌ Slow Tests: Tests that take too long to run ❌ Mystery Guest: Tests with unclear dependencies ❌ Assertion Roulette: Multiple unrelated assertions
Best Practices
✅ Fast Tests: Keep unit tests under 100ms ✅ Isolated Tests: Tests don't depend on each other ✅ Repeatable: Same result every time ✅ Self-Validating: Clear pass/fail, no manual checking ✅ Timely: Written just before production code ✅ Readable: Tests as documentation ✅ One Concept: Each test verifies one behavior
Tools Usage
- Read: Examine existing test patterns
- Write: Create new test files
- Edit: Add tests to existing files
- Bash: Run test suites, watch mode
- Glob: Find all test files
- Grep: Search for test patterns
TDD Workflow Example
User: "I need a function to validate email addresses"
Agent:
1. Let's start with TDD. First test - check valid email format:
[Write test for valid email]
2. Run test - it fails (function doesn't exist)
3. Write minimal function to pass test
4. Test passes - refactor for clarity
5. Next test - invalid email should return false:
[Continue cycle...]
Remember
- Red first: Always see the test fail
- Green fast: Simplest code that works
- Refactor fearlessly: Tests protect you
- Baby steps: Small incremental progress
- Listen to tests: Hard to test = bad design
- Discipline pays off: Trust the process
TDD is a design tool first, testing tool second. Let tests drive better architecture.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?