| name | python-testing |
| description | Python testing with pytest, TDD workflow, fixtures, mocking, parametrization, and coverage. Use when writing, reviewing, or debugging Python tests or setting up pytest infrastructure. |
| origin | MCC |
Python Testing Patterns
Idiomatic Python testing using pytest and TDD methodology. Tests are code too — keep them clean, readable, and maintainable.
When to Activate
- Writing new Python code (follow TDD: red, green, refactor)
- Designing test suites for Python projects
- Reviewing Python test coverage
- Setting up testing infrastructure
TDD Workflow
RED -> Write a failing test for the desired behavior
GREEN -> Write minimal code to make the test pass
REFACTOR -> Improve code while keeping tests green
def test_add_numbers():
result = add(2, 3)
assert result == 5
def add(a, b):
return a + b
Coverage Requirements
- Target: 80%+ code coverage
- Critical paths: 100% coverage required
pytest --cov=mypackage --cov-report=term-missing --cov-report=html
pytest Fundamentals
import pytest
def test_addition():
assert 2 + 2 == 4
with pytest.raises(ValueError, match="invalid input"):
validate_input("invalid")
with pytest.raises(CustomError) as exc_info:
raise CustomError("error", code=400)
assert exc_info.value.code == 400
Fixtures
@pytest.fixture
def sample_data():
return {"name": "Alice", "age": 30}
@pytest.fixture
def database():
db = Database(":memory:")
db.create_tables()
yield db
db.close()
def test_query(database):
result = database.query("SELECT * FROM users")
assert len(result) > 0
Fixture scopes: function (default, per-test), module (once per module), session (once per run).
See fixtures-and-mocking.md for conftest patterns, autouse, parametrized fixtures, and mocking.
Parametrization
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("PyThOn", "PYTHON"),
])
def test_uppercase(input, expected):
assert input.upper() == expected
@pytest.mark.parametrize("input,expected", [
("valid@email.com", True),
("invalid", False),
], ids=["valid-email", "missing-at"])
def test_email_validation(input, expected):
assert is_valid_email(input) is expected
Mocking
from unittest.mock import patch, Mock
@patch("mypackage.external_api_call")
def test_with_mock(api_call_mock):
api_call_mock.return_value = {"status": "success"}
result = my_function()
api_call_mock.assert_called_once()
assert result["status"] == "success"
@patch("mypackage.api_call")
def test_error_handling(api_call_mock):
api_call_mock.side_effect = ConnectionError("Network error")
with pytest.raises(ConnectionError):
api_call()
See fixtures-and-mocking.md for autospec, context managers, async mocking, and class mocking.
Async Testing
@pytest.mark.asyncio
async def test_async_function():
result = await async_add(2, 3)
assert result == 5
@pytest.mark.asyncio
@patch("mypackage.async_api_call")
async def test_async_mock(api_call_mock):
api_call_mock.return_value = {"status": "ok"}
result = await my_async_function()
api_call_mock.assert_awaited_once()
Markers and Test Selection
@pytest.mark.slow
def test_slow_operation():
time.sleep(5)
@pytest.mark.integration
def test_api_integration():
response = requests.get("https://api.example.com")
assert response.status_code == 200
pytest -m "not slow"
pytest -m integration
pytest -m "unit and not slow"
Common Patterns
API Testing
@pytest.fixture
def client():
app = create_app(testing=True)
return app.test_client()
def test_create_user(client):
response = client.post("/api/users", json={"name": "Alice", "email": "alice@example.com"})
assert response.status_code == 201
assert response.json["name"] == "Alice"
Database Testing
@pytest.fixture
def db_session():
session = Session(bind=engine)
session.begin_nested()
yield session
session.rollback()
session.close()
See common-patterns.md for file testing, class testing, and test organization.
Test Organization
tests/
├── conftest.py # Shared fixtures
├── unit/
│ ├── test_models.py
│ └── test_services.py
├── integration/
│ └── test_api.py
└── e2e/
└── test_user_flow.py
Running Tests
pytest
pytest tests/test_utils.py
pytest tests/test_utils.py::test_func
pytest -v
pytest --cov=mypackage --cov-report=html
pytest -x
pytest --lf
pytest -k "test_user"
pytest --pdb
Best Practices
DO — write tests before code (TDD), test one behavior per test, use descriptive names, use fixtures for setup, mock external deps, test edge cases, aim for 80%+ coverage
DON'T — test implementation details instead of behavior, share state between tests, ignore test failures, test third-party code, use print statements instead of assertions
Configuration
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = ["--strict-markers", "--cov=mypackage", "--cov-report=term-missing"]
markers = ["slow: slow tests", "integration: integration tests"]
Quick Reference
| Pattern | Usage |
|---|
pytest.raises() | Test expected exceptions |
@pytest.fixture() | Create reusable test fixtures |
@pytest.mark.parametrize() | Run tests with multiple inputs |
@patch() | Mock functions and classes |
tmp_path fixture | Automatic temp directory |
pytest --cov | Generate coverage report |
Reference Files