python-testing-patterns
wshobson/agents
Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development.
What is python-testing-patterns?
A guide to robust Python testing using pytest, fixtures, mocking, parameterization, and TDD practices. Use this skill when writing unit tests, setting up test suites, implementing test-driven development, creating integration tests, or establishing testing best practices.
- Write unit, integration, functional, and performance tests with pytest
- Organize tests using the AAA (Arrange-Act-Assert) pattern and naming conventions
- Mock external dependencies and services to isolate code under test
- Test retry behavior, time-dependent code, and async operations
- Measure and report test coverage with meaningful metrics
- Use test markers to categorize and selectively run tests
How to install python-testing-patterns
npx skills add https://github.com/wshobson/agents --skill python-testing-patterns- pytest installed (pip install pytest)
- pytest-cov for coverage reporting (pip install pytest-cov)
- freezegun for time mocking (pip install freezegun)
- unittest.mock available in Python standard library
How to use python-testing-patterns
- 1.Organize tests in a tests/ directory with conftest.py for shared fixtures
- 2.Write tests following the AAA pattern: Arrange, Act, Assert
- 3.Name tests descriptively using test_<unit>_<scenario>_<expected> convention
- 4.Use pytest markers (@pytest.mark) to categorize tests by type or speed
- 5.Run tests with pytest and generate coverage reports with --cov flag
- 6.Mock external dependencies using unittest.mock.Mock and side_effect
- 7.Use freezegun decorator to control time in time-dependent tests
- 8.Configure pytest.ini or pyproject.toml with coverage thresholds and test markers
Use cases
- Writing unit tests for individual functions and classes in isolation
- Setting up test suites and test infrastructure for Python projects
- Implementing test-driven development (TDD) workflows
- Creating integration tests for APIs and database operations
- Mocking external services and dependencies in tests
- Python developers writing tests
- QA engineers setting up test automation
- Teams adopting test-driven development
- Backend engineers testing APIs and services
- DevOps engineers configuring continuous testing
python-testing-patterns FAQ
Unit tests isolate individual functions/classes; integration tests verify interaction between components; functional tests validate complete features end-to-end. Use unit tests for fast feedback, integration tests for component interactions, and functional tests for user workflows.
Use unittest.mock.Mock to create mock objects. Set return_value for simple returns or side_effect for complex behavior (like raising exceptions or returning different values on successive calls). Verify calls with assert_called_with() or call_count.
Use the freezegun library with @freeze_time decorator to fix time at a specific moment. You can also use freeze_time as a context manager and call frozen_time.move_to() to advance time within a test.
Focus on meaningful coverage rather than hitting a specific percentage. Aim for 80%+ coverage of critical paths, but prioritize testing important business logic and edge cases over achieving high percentages. Use --cov-fail-under to enforce minimum thresholds in CI/CD.
Use @pytest.mark.skip(reason='...') to skip tests, @pytest.mark.skipif(...) for conditional skips, and @pytest.mark.xfail(reason='...') for expected failures. Create custom markers like @pytest.mark.slow or @pytest.mark.integration to categorize tests and run subsets with pytest -m.
Full instructions (SKILL.md)
Source of truth, from wshobson/agents.
name: python-testing-patterns description: Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites, or implementing testing best practices.
Python Testing Patterns
Comprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices.
When to Use This Skill
- Writing unit tests for Python code
- Setting up test suites and test infrastructure
- Implementing test-driven development (TDD)
- Creating integration tests for APIs and services
- Mocking external dependencies and services
- Testing async code and concurrent operations
- Setting up continuous testing in CI/CD
- Implementing property-based testing
- Testing database operations
- Debugging failing tests
Core Concepts
1. Test Types
- Unit Tests: Test individual functions/classes in isolation
- Integration Tests: Test interaction between components
- Functional Tests: Test complete features end-to-end
- Performance Tests: Measure speed and resource usage
2. Test Structure (AAA Pattern)
- Arrange: Set up test data and preconditions
- Act: Execute the code under test
- Assert: Verify the results
3. Test Coverage
- Measure what code is exercised by tests
- Identify untested code paths
- Aim for meaningful coverage, not just high percentages
4. Test Isolation
- Tests should be independent
- No shared state between tests
- Each test should clean up after itself
Quick Start
# test_example.py
def add(a, b):
return a + b
def test_add():
"""Basic test example."""
result = add(2, 3)
assert result == 5
def test_add_negative():
"""Test with negative numbers."""
assert add(-1, 1) == 0
# Run with: pytest test_example.py
Detailed patterns and worked examples
Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.
Testing Best Practices
Test Organization
# tests/
# __init__.py
# conftest.py # Shared fixtures
# test_unit/ # Unit tests
# test_models.py
# test_utils.py
# test_integration/ # Integration tests
# test_api.py
# test_database.py
# test_e2e/ # End-to-end tests
# test_workflows.py
Test Naming Convention
A common pattern: test_<unit>_<scenario>_<expected_outcome>. Adapt to your team's preferences.
# Pattern: test_<unit>_<scenario>_<expected>
def test_create_user_with_valid_data_returns_user():
...
def test_create_user_with_duplicate_email_raises_conflict():
...
def test_get_user_with_unknown_id_returns_none():
...
# Good test names - clear and descriptive
def test_user_creation_with_valid_data():
"""Clear name describes what is being tested."""
pass
def test_login_fails_with_invalid_password():
"""Name describes expected behavior."""
pass
def test_api_returns_404_for_missing_resource():
"""Specific about inputs and expected outcomes."""
pass
# Bad test names - avoid these
def test_1(): # Not descriptive
pass
def test_user(): # Too vague
pass
def test_function(): # Doesn't explain what's tested
pass
Testing Retry Behavior
Verify that retry logic works correctly using mock side effects.
from unittest.mock import Mock
def test_retries_on_transient_error():
"""Test that service retries on transient failures."""
client = Mock()
# Fail twice, then succeed
client.request.side_effect = [
ConnectionError("Failed"),
ConnectionError("Failed"),
{"status": "ok"},
]
service = ServiceWithRetry(client, max_retries=3)
result = service.fetch()
assert result == {"status": "ok"}
assert client.request.call_count == 3
def test_gives_up_after_max_retries():
"""Test that service stops retrying after max attempts."""
client = Mock()
client.request.side_effect = ConnectionError("Failed")
service = ServiceWithRetry(client, max_retries=3)
with pytest.raises(ConnectionError):
service.fetch()
assert client.request.call_count == 3
def test_does_not_retry_on_permanent_error():
"""Test that permanent errors are not retried."""
client = Mock()
client.request.side_effect = ValueError("Invalid input")
service = ServiceWithRetry(client, max_retries=3)
with pytest.raises(ValueError):
service.fetch()
# Only called once - no retry for ValueError
assert client.request.call_count == 1
Mocking Time with Freezegun
Use freezegun to control time in tests for predictable time-dependent behavior.
from freezegun import freeze_time
from datetime import datetime, timedelta
@freeze_time("2026-01-15 10:00:00")
def test_token_expiry():
"""Test token expires at correct time."""
token = create_token(expires_in_seconds=3600)
assert token.expires_at == datetime(2026, 1, 15, 11, 0, 0)
@freeze_time("2026-01-15 10:00:00")
def test_is_expired_returns_false_before_expiry():
"""Test token is not expired when within validity period."""
token = create_token(expires_in_seconds=3600)
assert not token.is_expired()
@freeze_time("2026-01-15 12:00:00")
def test_is_expired_returns_true_after_expiry():
"""Test token is expired after validity period."""
token = Token(expires_at=datetime(2026, 1, 15, 11, 30, 0))
assert token.is_expired()
def test_with_time_travel():
"""Test behavior across time using freeze_time context."""
with freeze_time("2026-01-01") as frozen_time:
item = create_item()
assert item.created_at == datetime(2026, 1, 1)
# Move forward in time
frozen_time.move_to("2026-01-15")
assert item.age_days == 14
Test Markers
# test_markers.py
import pytest
@pytest.mark.slow
def test_slow_operation():
"""Mark slow tests."""
import time
time.sleep(2)
@pytest.mark.integration
def test_database_integration():
"""Mark integration tests."""
pass
@pytest.mark.skip(reason="Feature not implemented yet")
def test_future_feature():
"""Skip tests temporarily."""
pass
@pytest.mark.skipif(os.name == "nt", reason="Unix only test")
def test_unix_specific():
"""Conditional skip."""
pass
@pytest.mark.xfail(reason="Known bug #123")
def test_known_bug():
"""Mark expected failures."""
assert False
# Run with:
# pytest -m slow # Run only slow tests
# pytest -m "not slow" # Skip slow tests
# pytest -m integration # Run integration tests
Coverage Reporting
# Install coverage
pip install pytest-cov
# Run tests with coverage
pytest --cov=myapp tests/
# Generate HTML report
pytest --cov=myapp --cov-report=html tests/
# Fail if coverage below threshold
pytest --cov=myapp --cov-fail-under=80 tests/
# Show missing lines
pytest --cov=myapp --cov-report=term-missing tests/
For advanced patterns (async testing, monkeypatching, property-based testing, database testing, CI/CD integration, and configuration), see references/advanced-patterns.md
Related skills
More from wshobson/agents and the wider catalog.
tailwind-design-system
Build production-ready design systems with Tailwind CSS v4, design tokens, and component libraries.
typescript-advanced-types
Master TypeScript's advanced type system: generics, conditional types, mapped types, and utility types for type-safe applications.
nodejs-backend-patterns
Build production-ready Node.js backends with Express/Fastify, middleware patterns, auth, and database integration.
python-performance-optimization
Profile and optimize Python code using cProfile, memory profilers, and performance best practices.
brand-landingpage
Brand-first landing page designer with guided interviews and Stitch-powered iteration.
api-design-principles
Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs.