pytest testing framework conventions and practices. Invoke whenever task involves any interaction with pytest — writing tests, configuring pytest, fixtures, parametrize, mocking, debugging test failures, or coverage.
pytest
Test behavior, not implementation. Tests are executable documentation — if the test name doesn't explain what the code
does, rewrite it.
pytest is Python's standard testing framework. It uses plain assert statements, fixtures for setup/teardown, and a
rich plugin ecosystem. All patterns target Python 3.14+.
See ${CLAUDE_SKILL_DIR}/references/parametrize.md for multi-parameter patterns, conditional skipping within
parametrize, and dynamic parametrize generation.
@pytest.mark.skipif(condition, reason="...") — skip when condition is true:
@pytest.mark.skipif(sys.platform == "win32", reason="Unix only").
@pytest.mark.xfail(reason="...") — expected failure. Passes if the test fails, reports unexpected pass if it
succeeds. Use strict=True to fail on unexpected pass.
@pytest.mark.usefixtures("fixture_name") — inject fixture without using its value.
mocker auto-restores after each test. Prefer over manual patch context managers.
mocker.patch("module.Class", autospec=True) — recursively specs all attributes and method signatures from the
real object. Catches signature mismatches at test time.
mocker.spy(obj, "method") wraps the real method — tracks calls while preserving behavior.
Mocking Rules
Mock at boundaries. Mock external services, databases, filesystems, clocks — not internal functions.
Don't mock what you own when a fake or in-memory implementation is available.
Prefer dependency injection over patching. Pass collaborators as parameters, mock in tests.
Never mock the thing you're testing. If you need to mock part of the SUT, the SUT has too many responsibilities —
split it.
Assertions
Plain Assert
pytest rewrites assert statements to show detailed failure messages:
assert result == expected # shows both values on failureassert"error"in message # shows the full stringassertlen(items) == 3# shows actual lengthassertall(x > 0for x in values) # shows the values
No assertion library needed. Plain assert with pytest's rewrite engine gives clear failure messages.
Multiple assertions per test are fine when they verify the same behavior.
Exception Testing
deftest_raises_on_invalid_input():
with pytest.raises(ValueError, match=r"must be positive"):
calculate(-1)
deftest_exception_attributes():
with pytest.raises(ValidationError) as exc_info:
validate(bad_data)
assert exc_info.value.field == "email"assert"invalid format"instr(exc_info.value)
Always use match= when the exception type is broad — validates the message.
Access .value for exception attributes via exc_info.
pytest.raises is a context manager. The code that raises must be inside the with.
Fixtures cascade downward. A fixture in tests/conftest.py is available to all tests. A fixture in
tests/unit/conftest.py is available only to unit tests.
Don't import from conftest. pytest discovers and injects conftest fixtures automatically.
Split by concern. Root conftest for shared utilities (factories, settings). Subdirectory conftest for
environment-specific setup (database, external services).
pytest-randomly — Randomize test order to catch hidden dependencies
See ${CLAUDE_SKILL_DIR}/references/plugins.md for configuration patterns and usage details.
Application
When writing tests: apply all conventions silently — don't narrate each rule being followed. Match the project's
existing test style. If an existing codebase contradicts a convention, follow the codebase and flag the divergence once.
When reviewing tests: cite the specific issue and show the fix inline. Don't lecture — state what's wrong and how to
fix it.
Bad: "According to pytest best practices, you should use fixtures
instead of setUp methods..."
Good: "setUp/tearDown -> @pytest.fixture with yield"
Integration
The python skill governs language choices; this skill governs pytest testing decisions. The coding skill governs
workflow (discovery, planning, verification).
Test behavior, not implementation. When in doubt, mock less.