python-tdd
Python TDD workflow - write pytest tests first, implement, verify 80%+ coverage
SKILL.md.claude/skills/python-tdd/SKILL.md
GitHubPython TDD Workflow
Enforce test-driven development for Python. Write pytest tests first, then implement. Verify 80%+ coverage with pytest --cov.
When to Use
- Implementing new Python functions/classes
- Adding test coverage to existing code
- Fixing bugs (write failing test first)
- Building critical business logic
TDD Cycle
1. RED - Write Failing Test
# tests/test_validator.py
import pytest
from mypackage.validator import validate_email
@pytest.mark.parametrize("email,expected", [
("user@example.com", True),
("invalid", False),
("", False),
("user@.com", False),
("@example.com", False),
("user@example", False),
])
def test_validate_email(email: str, expected: bool) -> None:
assert validate_email(email) == expected
2. GREEN - Write Minimal Implementation
# mypackage/validator.py
import re
def validate_email(email: str) -> bool:
if not email:
return False
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
3. REFACTOR - Improve While Tests Pass
pytest tests/test_validator.py -v
Test Patterns
Parametrized Tests
@pytest.mark.parametrize("input_val,expected", [
(1, "one"),
(2, "two"),
(3, "three"),
])
def test_number_to_word(input_val, expected):
assert number_to_word(input_val) == expected
Fixtures
@pytest.fixture
def db_session():
session = create_test_session()
yield session
session.rollback()
session.close()
def test_create_user(db_session):
user = create_user(db_session, name="Test")
assert user.name == "Test"
Mocking
from unittest.mock import AsyncMock, patch
@patch("mypackage.api.httpx.AsyncClient.get")
async def test_fetch_data(mock_get):
mock_get.return_value = AsyncMock(
status_code=200,
json=AsyncMock(return_value={"key": "value"})
)
result = await fetch_data()
assert result == {"key": "value"}
Async Tests
import pytest
@pytest.mark.asyncio
async def test_async_function():
result = await async_operation()
assert result is not None
Coverage Commands
# Run with coverage
pytest --cov=src --cov-report=term-missing
# Coverage by file
pytest --cov=src --cov-report=html
# Minimum coverage threshold
pytest --cov=src --cov-fail-under=80
# With race detection (for threaded code)
pytest -n auto # parallel execution with pytest-xdist
Coverage Targets
| Code Type | Target |
|---|---|
| Critical business logic | 100% |
| Public APIs | 90%+ |
| General code | 80%+ |
| Generated code | Exclude |
Best Practices
DO:
- Write test FIRST (before implementation)
- Use parametrized tests for multiple cases
- Test edge cases (empty, None, boundary values)
- Use fixtures for setup/teardown
- Test behavior, not implementation
- Use
pytest.raisesfor expected exceptions
DON'T:
- Write tests after implementation
- Test private methods directly
- Use
assert Trueorassert False - Share mutable state between tests
- Depend on test execution order
Related
/python-lint- Fix build and lint errors/python-review- Code review- Skills:
python-testing,tdd-workflow
Language
- Internal reasoning and thinking should be in English
- All user-facing output, reports, and summaries must be written in Japanese