python-testing-patterns
Python Testing Patterns
Overview
Guide pytest-based testing for Python services. Covers fixture design, factory patterns, mocking strategies, async testing, and parametrize usage. Applies the testing pyramid: unit tests for services, integration tests for repos and DB, end-to-end tests for API endpoints. Stack-specific Tier 3 reference skill.
Workflow
-
Read project setup — Check
.chalk/docs/engineering/for architecture docs. Determine the testing stack: pytest version, async framework (asyncio, trio), HTTP client (httpx, requests, aiohttp), ORM (SQLAlchemy, Django ORM, Tortoise), and any existing test conventions. Readconftest.pyfiles to understand current fixture organization. -
Identify the scope — Parse
$ARGUMENTSfor the specific module, test file, or testing concern. Categories include: fixture design, factory patterns, mocking, async testing, parametrize, or test organization. -
Audit fixture design — Check for:
- Fixture scope:
function(default, safest),class,module,session(fastest but risk shared state). Use the narrowest scope that avoids unacceptable slowness. conftest.pyorganization: fixtures in the rightconftest.pylevel (project root for shared, per-directory for scoped). Avoid importing fixtures across test directories.- Fixture dependencies: fixtures that depend on other fixtures should form a clean DAG, not a tangled web.
yieldfixtures for setup/teardown: prefer overaddfinalizerfor readability.
- Fixture scope:
-
Audit factory patterns — Check for:
- Inline test data construction (repeated
User(name="test", email="test@test.com")across tests). Recommendfactory_boyor custom factory functions. - Factory classes that mirror models:
UserFactory,OrderFactorywith sensible defaults and traits for variations. - Factories that hit the database when they do not need to (
factory.build()vs.factory.create()).
- Inline test data construction (repeated
-
Audit mocking strategy — Check for:
- Over-mocking: mocking the thing you are testing (tests pass but prove nothing).
- Under-mocking: tests that hit real external services (slow, flaky, costly).
- Mock placement: patch where the object is used, not where it is defined (
unittest.mock.patch("mymodule.requests.get")notpatch("requests.get")). - HTTP mocking:
respxforhttpx,responsesforrequests,aioresponsesforaiohttp. - Missing
spec=Trueon mocks (allows calling methods that do not exist on the real object).
-
Audit async testing — If the project uses async code, check for:
pytest-asynciowithmode="auto"or explicit@pytest.mark.asynciodecorators.- Missing
asyncon test functions that test async code (test passes silently without actually awaiting). - Async fixtures using
@pytest_asyncio.fixtureinstead of@pytest.fixture. - Event loop scope mismatches between fixtures and tests.
-
Audit parametrize usage — Check for:
- Duplicate test functions that differ only in input/output (should be
@pytest.mark.parametrize). - Missing edge case parameters: empty input, None, boundary values, unicode, very large inputs.
- Parametrize IDs for readable test output (
pytest.param(..., id="empty-list")).
- Duplicate test functions that differ only in input/output (should be
-
Assess testing pyramid — Check the balance:
- Unit tests (services, pure functions): fast, isolated, high coverage. Should be the majority.
- Integration tests (repos + DB, external service wrappers): test real interactions, use test databases or containers.
- E2E tests (API endpoints): test the full stack via
TestClient/httpx.AsyncClient. Should be the fewest, covering critical paths.
-
Report findings — Present findings with specific file references, current patterns, recommended improvements, and examples.
Output
- Format: Audit report delivered in the conversation
- Key sections: Fixture Organization, Factory Patterns, Mocking Strategy, Async Testing (if applicable), Parametrize Opportunities, Testing Pyramid Balance, Prioritized Recommendations with Code Examples
Anti-patterns
- Testing implementation, not behavior — Tests that assert on internal method calls or private attributes break when you refactor. Test the public interface and observable outcomes.
- Shared mutable state between tests — Session-scoped fixtures with mutable state cause tests to pass individually but fail together. Default to function scope; widen only with immutable data.
- Mock everything — If every dependency is mocked, the test proves the mock works, not the code. Integration tests exist for a reason. Mock external boundaries (APIs, databases in unit tests), not internal collaborators.
- No assertion messages — While
pytestprovides rich diffs for failures, complex assertions can benefit from a message explaining the intent of the check. For example,assert user.is_active, "New users should be active by default"is more informative than justassert user.is_active is True. - Ignoring test performance — A test suite that takes 10 minutes will not be run locally. Profile slow tests, use appropriate fixture scopes, and parallelize with
pytest-xdist. - Copy-paste test functions — Five test functions that differ by one line should be one parametrized test. Duplication in tests is just as harmful as duplication in production code.
More from generaljerel/chalk-skills
python-clean-architecture
Clean architecture patterns for Python services — service layer, repository pattern, domain models, dependency injection, error hierarchy, and testing strategy
22create-handoff
Generate a handoff document after implementation work is complete — summarizes changes, risks, and review focus areas for the review pipeline. Use when done coding and ready to hand off for review.
16create-review
Bootstrap a local AI review pipeline and generate a paste-ready review prompt for any reviewer agent. Use after creating a handoff or when ready to get an AI code review.
15fix-findings
Fix findings from the active review session — reads reviewer findings files, applies fixes by priority, and updates the resolution log. Use after pasting reviewer output into findings files.
15fix-review
When the user asks to fix, address, or work on PR review comments — fetch review comments from a GitHub pull request and apply fixes to the local codebase. Requires gh CLI.
15review-changes
End-to-end review pipeline — creates a handoff, generates a review (self-review or paste-ready for another provider), then offers to fix findings. Use when you want to review your changes before pushing.
13