| name | tdd-workflow |
| description | Guide developers through Test Driven Development workflow with red-green-refactor cycle, supporting multiple languages and frameworks Use when this capability is needed. |
| metadata | {"author":"darellchua2"} |
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-framework for generic test patterns
python-pytest-creator for Python-specific TDD
nextjs-unit-test-creator for 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:
pytest tests/test_auth.py -v
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:
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:
const login = (email, password) => {
return {
success: true,
data: { token: 'dummy_token' }
};
};
module.exports = { login };
Verify the test passes:
pytest tests/test_auth.py -v
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:
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:
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:
pytest tests/test_auth.py -v
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:
pip install pytest pytest-cov
mkdir -p tests
touch tests/__init__.py
TDD Workflow:
touch tests/test_feature.py
vim tests/test_feature.py
pytest tests/test_feature.py -v
vim feature.py
pytest tests/test_feature.py -v
vim feature.py
pytest tests/test_feature.py -v
Pytest-specific TDD tips:
- Use descriptive test names (snake_case)
- Use pytest fixtures for setup/teardown
- Use
@pytest.mark.parametrize for data-driven tests
- Use
pytest.raises for exception testing
- Use
pytest --cov for coverage checking
JavaScript/TypeScript TDD with Jest/Vitest
Setup (Jest):
npm install --save-dev jest
mkdir tests
Setup (Vitest):
npm install --save-dev vitest
mkdir tests
TDD Workflow:
touch feature.test.js
vim feature.test.js
npm test feature.test.js
vim feature.js
npm test feature.test.js
vim feature.js
npm test feature.test.js
Jest/Vitest-specific TDD tips:
- Use descriptive test names (descriptive strings)
- Use
describe blocks to group related tests
- Use
beforeEach/afterEach for setup/teardown
- Use
.toThrow() for exception testing
- Use watch mode for rapid TDD:
npm test -- --watch
Next.js TDD
Setup:
TDD for Next.js Components:
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/react for component testing
- Use
@testing-library/jest-dom for custom matchers
Best Practices
Test Organization
- One test file per module/component
- Group related tests with
describe blocks
- Use descriptive test names that explain the behavior
- Keep tests independent and isolated
- Arrange-Act-Assert (AAA) pattern:
user = create_user(email="test@example.com")
result = user.validate_email()
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:
assert user.is_authenticated() is True
assert user._token is not None
-
Keep tests small and focused:
def test_login_succeeds_with_valid_credentials():
...
def test_login_fails_with_invalid_password():
...
def test_login():
...
-
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 --cov or Jest coverage
- Review coverage reports for untested code
Verification Commands
After implementing TDD workflow, verify with these commands:
pytest tests/ -v --cov
npm test -- --coverage
test -f tests/test_feature.py && echo "✓ Test file exists"
grep "def test_" tests/test_feature.py | wc -l
test -f feature.py && echo "✓ Production code exists"
TDD Verification Checklist:
Example TDD Session
Feature: Validate Email Address
Step 1: Write failing test
def test_valid_email_format():
assert is_valid_email("user@example.com") is True
Step 2: Run test (fails)
$ pytest tests/test_email.py
Step 3: Write minimal code
def is_valid_email(email):
return True
Step 4: Run test (passes)
$ pytest tests/test_email.py
Step 5: Refactor
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
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
Step 9: Write minimal code (already done!)
Step 10: Run test (passes)
$ pytest tests/test_email.py
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.