What I do
I guide developers through the complete Test Driven Development (TDD) workflow by:
- Explain TDD Principles: Teach the core red-green-refactor cycle and its purpose
- Provide Language-Specific Guidance: Offer TDD workflows for Python/pytest, JavaScript/Jest/Vitest, Next.js, and other frameworks
- Define Test Structure: Show how to structure tests for TDD (failing tests first, minimal implementation, refactoring)
- Guide Through Workflow Steps: Walk through Write Test → Run Test (Red) → Write Minimal Code → Run Test (Green) → Refactor cycle
- Provide Examples: Show concrete TDD examples with before/after code comparisons
- Integrate with Test Generation: Work seamlessly with test-generator-framework, python-pytest-creator, and nextjs-unit-test-creator
- Enforce TDD Best Practices: Promote test isolation, clear test names, and incremental development
When to use me
Use this skill when:
- You want to adopt Test Driven Development methodology for a new feature
- You need guidance on TDD workflow for a specific language or framework
- You're teaching or mentoring developers on TDD practices
- You want to refactor existing code using TDD techniques
- You need to verify your test coverage follows TDD principles
- You're setting up testing standards for a new project
- You want to ensure tests are written before implementation code
Use this before using test generation skills like:
test-generator-frameworkfor generic test patternspython-pytest-creatorfor Python-specific TDDnextjs-unit-test-creatorfor Next.js TDD
Prerequisites
- Basic understanding of unit testing concepts
- Access to testing framework for your language (pytest, Jest, Vitest, etc.)
- Knowledge of the programming language you're working with
- Familiarity with project structure and build tools
- Optional: Existing test generation skills for your language
TDD Principles
The Red-Green-Refactor Cycle
TDD follows a continuous cycle:
┌─────────────────────────────────────────┐
│ 1. Write a failing test (RED) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 2. Run test and see it fail (RED) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 3. Write minimal code to pass (GREEN) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 4. Run test and see it pass (GREEN) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 5. Refactor code (REFACTOR) │
└─────────────────────────────────────────┘
↓
(Return to step 1)
Core TDD Rules
- Never write production code without a failing test
- Write the minimal amount of code to make the test pass
- Refactor only after all tests pass
- Keep tests small and focused
- Write one test at a time
- Test behavior, not implementation details
Steps
Step 1: Choose Your Feature
Identify the feature or function you want to implement:
Example: "User login functionality"
Break down into behaviors:
- Validate email format
- Check if user exists
- Verify password matches
- Return authentication token on success
- Return error message on failure
Step 2: Write First Failing Test (RED)
Start with the simplest possible test case:
Python/pytest Example:
def test_login_with_valid_credentials():
"""User should be able to login with valid credentials."""
response = auth.login("user@example.com", "password123")
assert response.success is True
assert "token" in response.data
JavaScript/Jest Example:
test('user can login with valid credentials', () => {
const response = auth.login('user@example.com', 'password123');
expect(response.success).toBe(true);
expect(response.data).toHaveProperty('token');
});
Verify the test fails:
# Python
pytest tests/test_auth.py -v
# JavaScript
npm test auth.test.js
Expected output: Test should fail with an error like AttributeError: module 'auth' has no attribute 'login' or auth.login is not a function
Step 3: Write Minimal Production Code (GREEN)
Write just enough code to make the test pass, nothing more:
Python/pytest Example:
# auth.py
class AuthResponse:
def __init__(self, success, data):
self.success = success
self.data = data
def login(email, password):
return AuthResponse(success=True, data={"token": "dummy_token"})
JavaScript/Jest Example:
// auth.js
const login = (email, password) => {
return {
success: true,
data: { token: 'dummy_token' }
};
};
module.exports = { login };
Verify the test passes:
# Python
pytest tests/test_auth.py -v
# JavaScript
npm test auth.test.js
Expected output: Test should pass (1 passed)
Step 4: Refactor (REFACTOR)
Now that the test passes, improve the code quality without changing behavior:
Refactoring guidelines:
- Extract constants
- Improve variable names
- Remove code duplication
- Improve readability
- Add input validation
Python Refactored Example:
# auth.py
DUMMY_TOKEN = "dummy_token"
class AuthResponse:
def __init__(self, success, data):
self.success = success
self.data = data
def login(email, password):
if not email or not password:
raise ValueError("Email and password are required")
return AuthResponse(success=True, data={"token": DUMMY_TOKEN})
JavaScript Refactored Example:
// auth.js
const DUMMY_TOKEN = 'dummy_token';
const login = (email, password) => {
if (!email || !password) {
throw new Error('Email and password are required');
}
return {
success: true,
data: { token: DUMMY_TOKEN }
};
};
module.exports = { login };
Verify tests still pass:
# Python
pytest tests/test_auth.py -v
# JavaScript
npm test auth.test.js
Step 5: Add Next Test Case
Return to Step 2 for the next behavior:
Example: Test invalid credentials
def test_login_with_invalid_credentials():
"""User should not be able to login with invalid credentials."""
response = auth.login("user@example.com", "wrong_password")
assert response.success is False
assert "error" in response.data
Write minimal code to pass, then refactor. Continue until all behaviors are tested.
Language-Specific TDD Workflows
Python TDD with pytest
Setup:
# Install pytest
pip install pytest pytest-cov
# Create tests directory
mkdir -p tests
# Create __init__.py
touch tests/__init__.py
TDD Workflow:
# 1. Create test file
touch tests/test_feature.py
# 2. Write failing test
vim tests/test_feature.py
# 3. Run test (should fail)
pytest tests/test_feature.py -v
# 4. Write minimal production code
vim feature.py
# 5. Run test (should pass)
pytest tests/test_feature.py -v
# 6. Refactor code
vim feature.py
# 7. Run tests again (should still pass)
pytest tests/test_feature.py -v
# 8. Add next test
# Return to step 2
Pytest-specific TDD tips:
- Use descriptive test names (snake_case)
- Use pytest fixtures for setup/teardown
- Use
@pytest.mark.parametrizefor data-driven tests - Use
pytest.raisesfor exception testing - Use
pytest --covfor coverage checking
JavaScript/TypeScript TDD with Jest/Vitest
Setup (Jest):
# Install Jest
npm install --save-dev jest
# Create test directory
mkdir tests
# Add test script to package.json
# "test": "jest"
Setup (Vitest):
# Install Vitest
npm install --save-dev vitest
# Create test directory
mkdir tests
# Add test script to package.json
# "test": "vitest"
TDD Workflow:
# 1. Create test file
touch feature.test.js
# 2. Write failing test
vim feature.test.js
# 3. Run test (should fail)
npm test feature.test.js
# 4. Write minimal production code
vim feature.js
# 5. Run test (should pass)
npm test feature.test.js
# 6. Refactor code
vim feature.js
# 7. Run tests again (should still pass)
npm test feature.test.js
# 8. Add next test
# Return to step 2
Jest/Vitest-specific TDD tips:
- Use descriptive test names (descriptive strings)
- Use
describeblocks to group related tests - Use
beforeEach/afterEachfor setup/teardown - Use
.toThrow()for exception testing - Use watch mode for rapid TDD:
npm test -- --watch
Next.js TDD
Setup:
# Use test-generator-framework with nextjs-unit-test-creator
# Next.js standard setup includes testing infrastructure
TDD for Next.js Components:
// Button.test.tsx
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
});
Next.js-specific TDD tips:
- Test user interactions (click, form submit, etc.)
- Test component props and state
- Mock API calls with testing-library
- Use
@testing-library/reactfor component testing - Use
@testing-library/jest-domfor custom matchers
Best Practices
Test Organization
- One test file per module/component
- Group related tests with
describeblocks - Use descriptive test names that explain the behavior
- Keep tests independent and isolated
- Arrange-Act-Assert (AAA) pattern:
# Arrange: Set up test data and conditions
user = create_user(email="test@example.com")
# Act: Execute the function being tested
result = user.validate_email()
# Assert: Verify the expected outcome
assert result is True
Test Naming
Good test names:
- ✅
test_login_with_valid_credentials_succeeds - ✅
test_empty_email_raises_validation_error - ✅
user can login with correct password(Jest) - ✅
returns error when password is too short(Jest)
Bad test names:
- ❌
test_login - ❌
test_1 - ❌
testValidation
Writing Good Tests
Test behavior, not implementation:
# Good: Tests behavior assert user.is_authenticated() is True # Bad: Tests implementation assert user._token is not NoneKeep tests small and focused:
# Good: One behavior per test def test_login_succeeds_with_valid_credentials(): ... def test_login_fails_with_invalid_password(): ... # Bad: Multiple behaviors in one test def test_login(): # Tests success and failure in one test ...Use test data generators for edge cases:
@pytest.mark.parametrize("email,valid", [ ("user@example.com", True), ("invalid", False), ("", False), (None, False), ]) def test_email_validation(email, valid): assert is_valid_email(email) is valid
Refactoring Guidelines
- Refactor only when all tests pass
- Make small, incremental changes
- Run tests after each refactoring step
- Don't add new functionality during refactoring
- Use automated refactoring tools when available
- Maintain the same test coverage
Common Issues
Tests Don't Fail Initially
Issue: Tests pass immediately without writing production code
Cause: You're testing the wrong thing or implementation already exists
Solution:
- Ensure you're testing a new feature or behavior
- Check if the function/class already exists
- Write a test for a specific edge case or error condition
- Make the test more specific about expected behavior
Too Much Production Code Written
Issue: Writing more code than needed to make test pass
Cause: Getting ahead of TDD cycle, thinking about implementation
Solution:
- Focus only on making the failing test pass
- Return hard-coded values if necessary (they'll be replaced by next tests)
- Write the absolute minimum code
- Trust that future tests will drive more implementation
Tests Become Brittle
Issue: Tests break when refactoring code
Cause: Testing implementation details instead of behavior
Solution:
- Test public interfaces and behavior
- Avoid testing private methods
- Use mocking appropriately for external dependencies
- Focus on what the code should do, not how
Slow TDD Cycle
Issue: Writing and running tests takes too long
Solution:
- Use test frameworks with watch mode (Jest/Vitest
--watch, pytest-xdist) - Keep test files small and focused
- Use fixtures to reduce setup duplication
- Run only the test you're working on
- Consider unit tests over integration tests for TDD
Test Coverage Too Low
Issue: Missing edge cases and error handling
Solution:
- Write tests for all code paths
- Use parametrized tests for edge cases
- Test error conditions explicitly
- Check coverage with
pytest --covor Jest coverage - Review coverage reports for untested code
Verification Commands
After implementing TDD workflow, verify with these commands:
# Python TDD Verification
pytest tests/ -v --cov
# Expected: All tests pass, coverage 80%+
# JavaScript TDD Verification
npm test -- --coverage
# Expected: All tests pass, coverage 80%+
# Check test file exists and is named correctly
test -f tests/test_feature.py && echo "✓ Test file exists"
# Verify tests follow TDD principles
# Check for descriptive names
grep "def test_" tests/test_feature.py | wc -l
# Expected: Multiple test functions with descriptive names
# Verify production code exists
test -f feature.py && echo "✓ Production code exists"
TDD Verification Checklist:
- Test written before production code
- Test fails initially (RED)
- Minimal code written to pass test (GREEN)
- Code refactored after passing (REFACTOR)
- Test still passes after refactoring
- Tests are descriptive and focused
- Tests cover edge cases and errors
- No tests of implementation details
- All tests pass
- Code coverage is 80% or higher
Example TDD Session
Feature: Validate Email Address
Step 1: Write failing test
# tests/test_email.py
def test_valid_email_format():
assert is_valid_email("user@example.com") is True
Step 2: Run test (fails)
$ pytest tests/test_email.py
# FAILED - is_valid_email not defined
Step 3: Write minimal code
# email.py
def is_valid_email(email):
return True # Pass the test with minimal code
Step 4: Run test (passes)
$ pytest tests/test_email.py
# PASSED
Step 5: Refactor
# email.py
import re
def is_valid_email(email):
"""Validate email format using regex."""
if not email:
return False
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
Step 6: Run test (still passes)
$ pytest tests/test_email.py
# PASSED
Step 7: Add next test (invalid email)
def test_invalid_email_format():
assert is_valid_email("invalid") is False
Step 8: Run test (fails)
$ pytest tests/test_email.py
# FAILED - "invalid" passes validation incorrectly
Step 9: Write minimal code (already done!)
# The regex in is_valid_email handles this case
Step 10: Run test (passes)
$ pytest tests/test_email.py
# PASSED
Step 11: Add more edge cases
@pytest.mark.parametrize("email,expected", [
("", False),
(None, False),
("user@", False),
("@example.com", False),
("user@example", False),
("user@example.", False),
])
def test_email_edge_cases(email, expected):
assert is_valid_email(email) is expected
Continue until all behaviors are tested!
Converted and distributed by TomeVault — claim your Tome and manage your conversions.