Implement comprehensive testing strategies with pytest
Python testing patterns

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.
--- 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
Overview
Comprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices.
Manual install (npx, ZIP)
npx skills add wshobson/agents --skill python-testing-patterns -g -a claude-codemkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/wshobson/agents/tar.gz/4236bb91f839 \
| tar -xz -C ~/.claude/skills --strip-components=4 agents-4236bb91f839/plugins/python-development/skills/python-testing-patternsNew-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/a7ef216e916162a5374b/python-testing-patterns.zip" -OutFile "$env:TEMP\python-testing-patterns.zip"; Expand-Archive "$env:TEMP\python-testing-patterns.zip" -DestinationPath "$HOME\.claude\skills" -ForceConnects with Agent Alley MCP · agentalley.io/mcp
Files included
SKILL.md7.1 KBreferences/2 files · 18.3 KB
Read the files here
Show the full text279 lines
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
| 1 | |
| 2 | name python-testing-patterns |
| 3 | 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. |
| 4 | |
| 5 | |
| 6 | # Python Testing Patterns |
| 7 | |
| 8 | Comprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices. |
| 9 | |
| 10 | ## When to Use This Skill |
| 11 | |
| 12 | Writing unit tests for Python code |
| 13 | Setting up test suites and test infrastructure |
| 14 | Implementing test-driven development (TDD) |
| 15 | Creating integration tests for APIs and services |
| 16 | Mocking external dependencies and services |
| 17 | Testing async code and concurrent operations |
| 18 | Setting up continuous testing in CI/CD |
| 19 | Implementing property-based testing |
| 20 | Testing database operations |
| 21 | Debugging failing tests |
| 22 | |
| 23 | ## Core Concepts |
| 24 | |
| 25 | ### 1. Test Types |
| 26 | |
| 27 | **Unit Tests**: Test individual functions/classes in isolation |
| 28 | **Integration Tests**: Test interaction between components |
| 29 | **Functional Tests**: Test complete features end-to-end |
| 30 | **Performance Tests**: Measure speed and resource usage |
| 31 | |
| 32 | ### 2. Test Structure (AAA Pattern) |
| 33 | |
| 34 | **Arrange**: Set up test data and preconditions |
| 35 | **Act**: Execute the code under test |
| 36 | **Assert**: Verify the results |
| 37 | |
| 38 | ### 3. Test Coverage |
| 39 | |
| 40 | Measure what code is exercised by tests |
| 41 | Identify untested code paths |
| 42 | Aim for meaningful coverage, not just high percentages |
| 43 | |
| 44 | ### 4. Test Isolation |
| 45 | |
| 46 | Tests should be independent |
| 47 | No shared state between tests |
| 48 | Each test should clean up after itself |
| 49 | |
| 50 | ## Quick Start |
| 51 | |
| 52 | |
| 53 | # test_example.py |
| 54 | def add(a, b): |
| 55 | return a + b |
| 56 | |
| 57 | def test_add(): |
| 58 | """Basic test example.""" |
| 59 | result = add(2, 3) |
| 60 | assert result == 5 |
| 61 | |
| 62 | def test_add_negative(): |
| 63 | """Test with negative numbers.""" |
| 64 | assert add(-1, 1) == 0 |
| 65 | |
| 66 | # Run with: pytest test_example.py |
| 67 | |
| 68 | |
| 69 | ## Detailed patterns and worked examples |
| 70 | |
| 71 | Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient. |
| 72 | |
| 73 | ## Testing Best Practices |
| 74 | |
| 75 | ### Test Organization |
| 76 | |
| 77 | |
| 78 | # tests/ |
| 79 | # __init__.py |
| 80 | # conftest.py # Shared fixtures |
| 81 | # test_unit/ # Unit tests |
| 82 | # test_models.py |
| 83 | # test_utils.py |
| 84 | # test_integration/ # Integration tests |
| 85 | # test_api.py |
| 86 | # test_database.py |
| 87 | # test_e2e/ # End-to-end tests |
| 88 | # test_workflows.py |
| 89 | |
| 90 | |
| 91 | ### Test Naming Convention |
| 92 | |
| 93 | A common pattern: `test_<unit>_<scenario>_<expected_outcome>`. Adapt to your team's preferences. |
| 94 | |
| 95 | |
| 96 | # Pattern: test_<unit>_<scenario>_<expected> |
| 97 | def test_create_user_with_valid_data_returns_user(): |
| 98 | ... |
| 99 | |
| 100 | def test_create_user_with_duplicate_email_raises_conflict(): |
| 101 | ... |
| 102 | |
| 103 | def test_get_user_with_unknown_id_returns_none(): |
| 104 | ... |
| 105 | |
| 106 | # Good test names - clear and descriptive |
| 107 | def test_user_creation_with_valid_data(): |
| 108 | """Clear name describes what is being tested.""" |
| 109 | pass |
| 110 | |
| 111 | def test_login_fails_with_invalid_password(): |
| 112 | """Name describes expected behavior.""" |
| 113 | pass |
| 114 | |
| 115 | def test_api_returns_404_for_missing_resource(): |
| 116 | """Specific about inputs and expected outcomes.""" |
| 117 | pass |
| 118 | |
| 119 | # Bad test names - avoid these |
| 120 | def test_1(): # Not descriptive |
| 121 | pass |
| 122 | |
| 123 | def test_user(): # Too vague |
| 124 | pass |
| 125 | |
| 126 | def test_function(): # Doesn't explain what's tested |
| 127 | pass |
| 128 | |
| 129 | |
| 130 | ### Testing Retry Behavior |
| 131 | |
| 132 | Verify that retry logic works correctly using mock side effects. |
| 133 | |
| 134 | |
| 135 | from unittest.mock import Mock |
| 136 | |
| 137 | def test_retries_on_transient_error(): |
| 138 | """Test that service retries on transient failures.""" |
| 139 | client = Mock() |
| 140 | # Fail twice, then succeed |
| 141 | client.request.side_effect = [ |
| 142 | ConnectionError("Failed"), |
| 143 | ConnectionError("Failed"), |
| 144 | {"status": "ok"}, |
| 145 | ] |
| 146 | |
| 147 | service = ServiceWithRetry(client, max_retries=3) |
| 148 | result = service.fetch() |
| 149 | |
| 150 | assert result == {"status": "ok"} |
| 151 | assert client.request.call_count == 3 |
| 152 | |
| 153 | def test_gives_up_after_max_retries(): |
| 154 | """Test that service stops retrying after max attempts.""" |
| 155 | client = Mock() |
| 156 | client.request.side_effect = ConnectionError("Failed") |
| 157 | |
| 158 | service = ServiceWithRetry(client, max_retries=3) |
| 159 | |
| 160 | with pytest.raises(ConnectionError): |
| 161 | service.fetch() |
| 162 | |
| 163 | assert client.request.call_count == 3 |
| 164 | |
| 165 | def test_does_not_retry_on_permanent_error(): |
| 166 | """Test that permanent errors are not retried.""" |
| 167 | client = Mock() |
| 168 | client.request.side_effect = ValueError("Invalid input") |
| 169 | |
| 170 | service = ServiceWithRetry(client, max_retries=3) |
| 171 | |
| 172 | with pytest.raises(ValueError): |
| 173 | service.fetch() |
| 174 | |
| 175 | # Only called once - no retry for ValueError |
| 176 | assert client.request.call_count == 1 |
| 177 | |
| 178 | |
| 179 | ### Mocking Time with Freezegun |
| 180 | |
| 181 | Use freezegun to control time in tests for predictable time-dependent behavior. |
| 182 | |
| 183 | |
| 184 | from freezegun import freeze_time |
| 185 | from datetime import datetime, timedelta |
| 186 | |
| 187 | @freeze_time("2026-01-15 10:00:00") |
| 188 | def test_token_expiry(): |
| 189 | """Test token expires at correct time.""" |
| 190 | token = create_token(expires_in_seconds=3600) |
| 191 | assert token.expires_at == datetime(2026, 1, 15, 11, 0, 0) |
| 192 | |
| 193 | @freeze_time("2026-01-15 10:00:00") |
| 194 | def test_is_expired_returns_false_before_expiry(): |
| 195 | """Test token is not expired when within validity period.""" |
| 196 | token = create_token(expires_in_seconds=3600) |
| 197 | assert not token.is_expired() |
| 198 | |
| 199 | @freeze_time("2026-01-15 12:00:00") |
| 200 | def test_is_expired_returns_true_after_expiry(): |
| 201 | """Test token is expired after validity period.""" |
| 202 | token = Token(expires_at=datetime(2026, 1, 15, 11, 30, 0)) |
| 203 | assert token.is_expired() |
| 204 | |
| 205 | def test_with_time_travel(): |
| 206 | """Test behavior across time using freeze_time context.""" |
| 207 | with freeze_time("2026-01-01") as frozen_time: |
| 208 | item = create_item() |
| 209 | assert item.created_at == datetime(2026, 1, 1) |
| 210 | |
| 211 | # Move forward in time |
| 212 | frozen_time.move_to("2026-01-15") |
| 213 | assert item.age_days == 14 |
| 214 | |
| 215 | |
| 216 | ### Test Markers |
| 217 | |
| 218 | |
| 219 | # test_markers.py |
| 220 | import pytest |
| 221 | |
| 222 | @pytest.mark.slow |
| 223 | def test_slow_operation(): |
| 224 | """Mark slow tests.""" |
| 225 | import time |
| 226 | time.sleep(2) |
| 227 | |
| 228 | |
| 229 | @pytest.mark.integration |
| 230 | def test_database_integration(): |
| 231 | """Mark integration tests.""" |
| 232 | pass |
| 233 | |
| 234 | |
| 235 | @pytest.mark.skip(reason="Feature not implemented yet") |
| 236 | def test_future_feature(): |
| 237 | """Skip tests temporarily.""" |
| 238 | pass |
| 239 | |
| 240 | |
| 241 | @pytest.mark.skipif(os.name == "nt", reason="Unix only test") |
| 242 | def test_unix_specific(): |
| 243 | """Conditional skip.""" |
| 244 | pass |
| 245 | |
| 246 | |
| 247 | @pytest.mark.xfail(reason="Known bug #123") |
| 248 | def test_known_bug(): |
| 249 | """Mark expected failures.""" |
| 250 | assert False |
| 251 | |
| 252 | |
| 253 | # Run with: |
| 254 | # pytest -m slow # Run only slow tests |
| 255 | # pytest -m "not slow" # Skip slow tests |
| 256 | # pytest -m integration # Run integration tests |
| 257 | |
| 258 | |
| 259 | ### Coverage Reporting |
| 260 | |
| 261 | |
| 262 | # Install coverage |
| 263 | pip install pytest-cov |
| 264 | |
| 265 | # Run tests with coverage |
| 266 | pytest --cov=myapp tests/ |
| 267 | |
| 268 | # Generate HTML report |
| 269 | pytest --cov=myapp --cov-report=html tests/ |
| 270 | |
| 271 | # Fail if coverage below threshold |
| 272 | pytest --cov=myapp --cov-fail-under=80 tests/ |
| 273 | |
| 274 | # Show missing lines |
| 275 | pytest --cov=myapp --cov-report=term-missing tests/ |
| 276 | |
| 277 | |
| 278 | For advanced patterns (async testing, monkeypatching, property-based testing, database testing, CI/CD integration, and configuration), see [references/advanced-patterns.md] |
| 279 |
Details
- Updated
- Sep 21, 2026
- License
- MIT
- Stars on the repo
- ★ 39,857
- Checked
- Source
- wshobson/agents ↗
- Creator
- wshobson · 179 listings
Similar skills
See all →
Skill29 production-ready scripts for iOS app testing, buildingIos simulator skill
SkillMoAI unified orchestrator for autonomous development
SkillWrite Android unit, Compose UI, and Hilt-integrated testsAndroid Testing Standards
SkillImplementing any feature or bugfixTest-Driven Development (TDD)
SkillToolkit for interacting with and testing local webWebapp testing
PromptGuide to fixing bugs using a test-first approachTest first bug fixing approachMore from wshobson
SkillMaster C/.NET backend development patterns for building
SkillImplement WCAG 2.2 compliant interfaces with mobileAccessibility compliance
SkillMaster Material Design 3 and Jetpack Compose patternsAndroid Mobile Design
SkillMigrate from AngularJS to Angular using hybrid modeAngular migration
SkillUnderstand anti-reversing, obfuscationAnti reversing techniques
SkillBuild production Apache Airflow DAGs with best practicesApache Airflow DAG Patterns