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.
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
3 files · 25 KB7 KB
Install
It’s free, and every skill you add syncs into every AI tool on your computer, instantly.
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.pydefadd(a, b):
return a + b
deftest_add():
"""Basic test example."""
result = add(2, 3)
assert result == 5deftest_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.
A common pattern: test_<unit>_<scenario>_<expected_outcome>. Adapt to your team's preferences.
# Pattern: test_<unit>_<scenario>_<expected>deftest_create_user_with_valid_data_returns_user():
...
deftest_create_user_with_duplicate_email_raises_conflict():
...
deftest_get_user_with_unknown_id_returns_none():
...
# Good test names - clear and descriptivedeftest_user_creation_with_valid_data():
"""Clear name describes what is being tested."""passdeftest_login_fails_with_invalid_password():
"""Name describes expected behavior."""passdeftest_api_returns_404_for_missing_resource():
"""Specific about inputs and expected outcomes."""pass# Bad test names - avoid thesedeftest_1(): # Not descriptivepassdeftest_user(): # Too vaguepassdeftest_function(): # Doesn't explain what's testedpass
Testing Retry Behavior
Verify that retry logic works correctly using mock side effects.
from unittest.mock import Mock
deftest_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 == 3deftest_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 == 3deftest_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 ValueErrorassert 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")deftest_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")deftest_is_expired_returns_false_before_expiry():
"""Test token is not expired when within validity period."""
token = create_token(expires_in_seconds=3600)
assertnot token.is_expired()
@freeze_time("2026-01-15 12:00:00")deftest_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()
deftest_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.pyimport pytest
@pytest.mark.slowdeftest_slow_operation():
"""Mark slow tests."""import time
time.sleep(2)
@pytest.mark.integrationdeftest_database_integration():
"""Mark integration tests."""pass@pytest.mark.skip(reason="Feature not implemented yet")deftest_future_feature():
"""Skip tests temporarily."""pass@pytest.mark.skipif(os.name == "nt", reason="Unix only test")deftest_unix_specific():
"""Conditional skip."""pass@pytest.mark.xfail(reason="Known bug #123")deftest_known_bug():
"""Mark expected failures."""assertFalse# 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