Testing Best Practices
Rules for a pytest suite that is fast, reliable, and maintainable.
How to Use This List
- Apply when writing new tests or reviewing PRs
- Prioritize isolation and speed before coverage percentage
- Revisit when adding integration or E2E tests
A - Test Design
- One behavior per test. Each test fails for exactly one reason.
- Arrange-act-assert structure. Readable without comments explaining layout.
- Descriptive test names.
test_discount_over_100_raises, nottest_discount_3. - Test behavior, not implementation. Assert outputs and public API, not internal calls.
B - Isolation & Speed
- No network in unit tests. Mock HTTP, DNS, and external APIs.
- Fresh DB state per test. Roll back transactions or use in-memory SQLite.
- No sleep() for timing. Use mocks for time or event-based waiting.
- Mark slow tests.
@pytest.mark.slowexcluded from default CI run.
C - Fixtures & Data
- Shared setup in fixtures, not copy-paste.
conftest.pyfor reuse. - Factory fixtures for flexible data.
user_factory(email="...")over static dicts. - Function scope by default. Broader scopes only for expensive immutable setup.
- Parametrize known cases; Hypothesis for exploration. Do not loop inside tests.
D - CI & Coverage
- pytest runs on every PR. Required check, not optional.
- Coverage on
src/only. 80% floor; higher for auth and billing modules. - Branch coverage enabled.
branch = truein coverage config. - Multi-version matrix for libraries. Test
requires-pythonbounds with nox or CI matrix.
E - Maintenance
- Delete tests for removed features. Dead tests hide missing coverage.
- Fix flaky tests immediately. Quarantine with
@pytest.mark.flakyonly as temporary measure. - Mock at boundaries, not everywhere. Too many mocks mean testing mocks.
- Integration tests separate from unit tests.
tests/unit/andtests/integration/directories.
FAQs
How fast should unit tests be?
Full unit suite under 60 seconds. Individual tests under 100ms.
What ratio of unit to integration tests?
Roughly 80/20. Most tests unit; integration covers critical paths.
Should I test private functions?
Test through public API. Private helpers are covered indirectly.
How do I handle test data files?
tests/fixtures/data/ with path via pathlib.Path(__file__).parent.
Snapshot testing?
Useful for serializers and templates. Review snapshot updates carefully in PRs.
How many assertions per test?
One logical behavior. Multiple asserts ok if they verify one outcome.
Should tests use real HTTP?
Unit: no. Integration/staging: yes against test environment.
How do I test logging?
caplog fixture: with caplog.at_level(logging.ERROR):.
Parallel tests?
pytest-xdist -n auto for large suites. Ensure test isolation first.
When to skip a test?
@pytest.mark.skip with reason for platform-specific or unfinished work. Not for failing tests.
Related
- pytest Setup - configuration
- Assertions & Test Structure - test layout
- Coverage & Reporting - coverage policy
- Mocking & Patching - isolation boundaries
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.