| name | python-testing |
| description | Python testing best practices covering pytest, unittest.mock, property-based testing with Hypothesis, fixtures, and mocking patterns |
Python Testing Best Practices
Expert-level guidance for writing effective, maintainable Python tests.
The Zen of Testing
Tests should be fast and deterministic.
Each test should verify one thing.
Tests are documentation—make them readable.
Mock at boundaries, not implementation details.
Prefer integration over unit when boundaries are unclear.
Failing tests should tell you what broke.
Test behavior, not implementation.
Test Organization
Directory Structure
tests/
├── conftest.py # Shared fixtures, plugins
├── test_module.py # Tests for module.py
├── test_feature/ # Feature-specific tests
│ ├── conftest.py # Feature-specific fixtures
│ └── test_component.py
└── fixtures/ # Test data files
└── sample_data.json
Naming Conventions
| Element | Convention | Example |
|---|
| Test files | test_*.py or *_test.py | test_calculator.py |
| Test classes | Test* (optional) | TestCalculator |
| Test functions | test_* | test_add_positive_numbers |
| Fixtures | Descriptive nouns | mock_database, sample_user |
Use descriptive names: test_<what>_<condition>_<expected>
def test_divide_by_zero_raises_exception() -> None: ...
def test_login_with_invalid_password_returns_401() -> None: ...
pytest Quick Reference
AAA Pattern
def test_user_creation() -> None:
email = "test@example.com"
user = User.create(email=email)
assert user.email == email
Key Assertions
assert value == expected
assert value is None / is not None
assert value in collection
assert value == pytest.approx(expected, rel=1e-3)
with pytest.raises(ValueError) as exc_info:
function_that_raises()
Parametrized Tests
@pytest.mark.parametrize("input_val,expected", [
(1, 2), (2, 4), (-1, -2),
])
def test_double(input_val: int, expected: int) -> None:
assert double(input_val) == expected
Common Markers
@pytest.mark.slow
@pytest.mark.skip(reason="")
@pytest.mark.skipif(cond)
@pytest.mark.xfail
@pytest.mark.asyncio
See: references/PYTEST.md for fixtures, scopes, factories, teardown patterns.
unittest.mock Essentials
Core Concepts
from unittest.mock import Mock, MagicMock, patch, AsyncMock
mock = Mock(return_value=42)
mock = Mock(side_effect=[1, 2, 3])
mock = Mock(side_effect=ValueError)
Where to Patch (Critical!)
Patch where the object is looked up, not where it's defined:
from datetime import datetime
def get_time(): return datetime.now()
@patch("mymodule.datetime")
def test_time(mock_dt): ...
@patch("datetime.datetime")
def test_wrong(mock_dt): ...
Key Assertions
mock.assert_called()
mock.assert_called_once()
mock.assert_called_with(*args, **kwargs)
mock.assert_called_once_with(*args, **kwargs)
mock.assert_not_called()
mock.assert_has_calls([call(...), call(...)])
Patch Variants
@patch("module.Class")
@patch.object(Class, "method")
@patch.dict(os.environ, {"K": "V"})
@patch("module.Class", autospec=True)
See: references/MOCKING.md for AsyncMock, mock_open, ANY matcher, autospec details.
Hypothesis (Property-Based Testing)
Generate test cases to find edge cases automatically:
from hypothesis import given, strategies as st
@given(st.integers())
def test_abs_non_negative(n: int) -> None:
assert abs(n) >= 0
@given(st.lists(st.integers()))
def test_sort_length(lst: list[int]) -> None:
assert len(sorted(lst)) == len(lst)
See: references/HYPOTHESIS.md for strategies, assume(), composite strategies.
Async Testing
@pytest.mark.asyncio
async def test_async_function() -> None:
result = await async_fetch()
assert result == expected
mock_client = AsyncMock()
mock_client.get.return_value = {"data": "value"}
mock_client.get.assert_awaited_once_with("/path")
See: references/ASYNC_AND_COVERAGE.md for fixtures, timeouts, coverage config.
Coverage
pytest --cov=mypackage --cov-fail-under=95 tests/
pytest --cov=mypackage --cov-report=html tests/
Quick Checklist
Before committing tests:
See: references/ANTI_PATTERNS.md for common mistakes to avoid.
Reference Files