Test-Driven Development
Test-Driven Development (TDD) Workflow
Overview
Test-Driven Development enforces the RED → GREEN → REFACTOR discipline to ensure robust, well-tested code with clean design. This skill guides implementation of the three-phase TDD cycle for both Python and TypeScript/JavaScript codebases.
TDD Three-Phase Cycle
Phase 1: RED - Write Failing Test
Write the smallest possible test that fails for the right reason.
Principles:
- Test documents the intended behavior
- Test must fail initially (verify failure)
- Focus on one specific behavior per test
- Use descriptive test names that explain the "should"
Test Structure:
// Arrange - Set up test data
// Act - Execute the behavior
// Assert - Verify the result
Phase 2: GREEN - Make Test Pass
Write the minimal implementation to make the test pass.
Principles:
- Implement only what's needed for the test to pass
- Don't optimize or refactor yet
- Hardcode values if necessary
- Focus on correctness, not elegance
Phase 3: REFACTOR - Clean Up Code
Improve the code while keeping tests green.
Focus Areas:
- Extract repeated logic into functions
- Improve naming and readability
- Apply SOLID principles
- Remove duplication
- Optimize performance if needed
Safety Net: Run tests after each refactoring step.
Implementation Workflow
Step 1: Understand Requirements
- Clarify the feature's expected behavior
- Identify edge cases and error conditions
- Define acceptance criteria
Step 2: Write Integration Test First
- Start with a higher-level test that describes user behavior
- Use realistic test data and scenarios
- Focus on the public interface
Step 3: Follow TDD for Implementation
- Write unit test (RED)
- Implement minimal solution (GREEN)
- Refactor and clean up (REFACTOR)
- Repeat for each behavior
Step 4: Verify Complete Coverage
- Run coverage report
- Ensure all new code paths are tested
- Add tests for any missing edge cases
Test Categories
Unit Tests
- Test individual functions/methods in isolation
- Use mocks for external dependencies
- Fast execution (< 100ms each)
- High coverage of business logic
Integration Tests
- Test component interactions
- Use real database/API connections where appropriate
- Validate end-to-end workflows
- Slower but comprehensive
Edge Case Tests
- Null/undefined inputs
- Empty collections
- Boundary values (min/max)
- Error conditions and exceptions
Language-Specific Patterns
Python (pytest)
def test_should_calculate_total_with_tax():
# Arrange
cart = ShoppingCart()
cart.add_item("item1", price=100.00)
# Act
total = cart.calculate_total(tax_rate=0.08)
# Assert
assert total == 108.00
TypeScript (Jest)
describe('ShoppingCart', () => {
it('should calculate total with tax', () => {
// Arrange
const cart = new ShoppingCart();
cart.addItem('item1', 100.00);
// Act
const total = cart.calculateTotal(0.08);
// Assert
expect(total).toBe(108.00);
});
});
Quality Gates
Before Moving to GREEN
- Test fails for the right reason
- Test name clearly describes the behavior
- Test is minimal and focused
- All existing tests still pass
Before Moving to REFACTOR
- New test passes
- Implementation is minimal
- No over-engineering
- All tests still pass
After REFACTOR
- Code is clean and readable
- No duplication
- SOLID principles applied
- All tests pass
- Coverage maintained
Common Pitfalls to Avoid
Writing Too Much Code in GREEN
Problem: Implementing multiple features at once Solution: Write only enough code to make the current test pass
Skipping the RED Phase
Problem: Writing tests after implementation Solution: Always write failing test first, verify it fails
Not Refactoring
Problem: Accumulating technical debt Solution: Refactor immediately after GREEN, while context is fresh
Testing Implementation Details
Problem: Tests break when refactoring Solution: Test public behavior, not internal implementation
Tools and Commands
Running Tests
# Python
pytest --cov=src tests/
pytest -v --cov-report=html
# TypeScript/Jest
npm test
npm run test:coverage
npm run test:watch
Coverage Thresholds
- Lines: 80% minimum
- Branches: 80% minimum
- Functions: 90% minimum
- New code: 100% coverage required
Additional Resources
Reference Files
For detailed patterns and advanced techniques, consult:
references/tdd-patterns.md- Common TDD patterns and best practicesreferences/testing-pyramid.md- Testing strategy and test typesreferences/mocking-strategies.md- Mock patterns for different scenarios
Example Files
Working TDD examples in examples/:
examples/python-tdd-example.py- Complete Python TDD workflowexamples/typescript-tdd-example.ts- TypeScript TDD implementationexamples/api-endpoint-tdd.py- TDD for API endpoints
Scripts
Utility scripts in scripts/:
scripts/run-tdd-cycle.sh- Automated TDD cycle runnerscripts/coverage-check.sh- Coverage validation scriptscripts/test-watch.sh- Watch mode for continuous testing
TDD in Context
When to Use TDD
- New feature development
- Bug fixes with regression tests
- Refactoring existing code
- API design and validation
When to Consider Alternatives
- Exploratory coding/prototyping
- UI/UX experimentation
- Performance optimization spikes
- External system integration discovery
Success Metrics
Quantitative
- Code coverage > 80%
- Test execution time < 5 minutes
- Test failure rate < 5%
- Defect density reduction
Qualitative
- Confidence in refactoring
- Clear behavior documentation
- Faster debugging cycles
- Improved code design
Apply TDD discipline consistently to build robust, maintainable code with comprehensive test coverage and clean architecture.