Test-Driven Development (TDD)
Comprehensive TDD patterns and practices for all programming languages. This skill eliminates ~500-800 lines of redundant testing guidance per agent.
When to Use
Apply TDD for:
- New feature implementation
- Bug fixes (test the bug first)
- Code refactoring (tests ensure behavior preservation)
- API development (test contracts)
- Complex business logic
TDD Workflow (Red-Green-Refactor)
1. Red Phase: Write Failing Test
Write a test that:
- Describes the desired behavior
- Fails for the right reason (not due to syntax errors)
- Is focused on a single behavior
2. Green Phase: Make It Pass
Write the minimum code to:
- Pass the test
- Not introduce regressions
- Follow existing patterns
3. Refactor Phase: Improve Code
While keeping tests green:
- Remove duplication
- Improve naming
- Simplify logic
- Extract functions/classes
Test Structure Patterns
Arrange-Act-Assert (AAA)
// Arrange: Set up test data and conditions
const user = createTestUser({ role: 'admin' });
// Act: Perform the action being tested
const result = await authenticateUser(user);
// Assert: Verify the outcome
expect(result.isAuthenticated).toBe(true);
expect(result.permissions).toContain('admin');
Given-When-Then (BDD Style)
Given: A user with admin privileges
When: They attempt to access protected resource
Then: Access is granted with appropriate permissions
Test Naming Conventions
Pattern: test_should_<expected_behavior>_when_<condition>
Examples:
test_should_return_user_when_id_exists()
test_should_raise_error_when_user_not_found()
test_should_validate_email_format_when_creating_account()
Language-Specific Conventions
Python (pytest):
def test_should_calculate_total_when_items_added():
cart = ShoppingCart()
cart.add_item(Item("Book", 10.00))
cart.add_item(Item("Pen", 1.50))
total = cart.calculate_total()
assert total == 11.50
JavaScript (Jest):
describe('ShoppingCart', () => {
test('should calculate total when items added', () => {
const cart = new ShoppingCart();
cart.addItem({ name: 'Book', price: 10.00 });
cart.addItem({ name: 'Pen', price: 1.50 });
const total = cart.calculateTotal();
expect(total).toBe(11.50);
});
});
Go:
func TestShouldCalculateTotalWhenItemsAdded(t *testing.T) {
cart := NewShoppingCart()
cart.AddItem(Item{Name: "Book", Price: 10.00})
cart.AddItem(Item{Name: "Pen", Price: 1.50})
total := cart.CalculateTotal()
if total != 11.50 {
t.Errorf("Expected 11.50, got %f", total)
}
}
Test Types and Scope
Unit Tests
- Scope: Single function/method
- Dependencies: Mocked
- Speed: Fast (< 10ms per test)
- Coverage: 80%+ of code paths
Integration Tests
- Scope: Multiple components
- Dependencies: Real or test doubles
- Speed: Moderate (< 1s per test)
- Coverage: Critical paths and interfaces
End-to-End Tests
- Scope: Full user workflows
- Dependencies: Real (in test environment)
- Speed: Slow (seconds to minutes)
- Coverage: Core user journeys
Mocking and Test Doubles
When to Mock
- External APIs and services
- Database operations (for unit tests)
- File system operations
- Time-dependent operations
- Random number generation
Mock Types
Stub: Returns predefined data
def get_user_stub(user_id):
return User(id=user_id, name="Test User")
Mock: Verifies interactions
mock_service = Mock()
service.process_payment(payment_data)
mock_service.process_payment.assert_called_once_with(payment_data)
Fake: Working implementation (simplified)
class FakeDatabase:
def __init__(self):
self.data = {}
def save(self, key, value):
self.data[key] = value
def get(self, key):
return self.data.get(key)
Test Coverage Guidelines
Target Coverage Levels
- Critical paths: 100%
- Business logic: 95%+
- Overall project: 80%+
- UI components: 70%+
What to Test
- โ
Business logic and algorithms
- โ
Edge cases and boundary conditions
- โ
Error handling and validation
- โ
State transitions
- โ
Public APIs and interfaces
What NOT to Test
- โ Framework internals
- โ Third-party libraries
- โ Trivial getters/setters
- โ Generated code
- โ Configuration files
Testing Best Practices
1. One Assertion Per Test (When Possible)
def test_should_validate_email_format():
assert is_valid_email("[email protected]") is True
def test_validation():
assert is_valid_email("[email protected]") is True
assert is_valid_phone("123-456-7890") is True
2. Test Independence
def test_user_creation():
user = create_user("[email protected]")
assert user.email == "[email protected]"
shared_user = None
def test_create_user():
global shared_user
shared_user = create_user("[email protected]")
def test_update_user():
shared_user.name = "Updated"
3. Descriptive Test Failures
assert result.status == 200, f"Expected 200, got {result.status}: {result.body}"
assert result.status == 200
4. Test Data Builders
def create_test_user(**overrides):
defaults = {
'email': '[email protected]',
'name': 'Test User',
'role': 'user'
}
return User(**{**defaults, **overrides}