Files
prompts/docs/skills/pytest-scaffolding/SKILL.md
T
John Lancaster 5c4de7b721 pytest skill
2026-06-19 17:39:57 -05:00

5.0 KiB

name, description, argument-hint
name description argument-hint
pytest-scaffolding Scaffold a maintainable, hierarchical pytest suite with fast defaults and clear escalation paths for FastAPI and SQLAlchemy tests. Use when creating or reorganizing tests, defining fixture/marker boundaries, or making test strategy progressively discoverable. Target scope plus stack details (pure Python, FastAPI, SQLAlchemy sync, SQLAlchemy async, or mixed)

Pytest Scaffolding

Create test scaffolding that stays fast for daily work and scales safely as dependencies increase.

This skill is optimized for progressive discoverability:

  1. Start with the shortest path in this file.
  2. Load exactly one deeper reference only when a decision requires it.
  3. Continue only as far as needed for the current task.

Repository defaults:

  • uv run pytest is the canonical invocation.
  • pytest settings live in pyproject.toml under [tool.pytest.ini_options].
  • strict marker checking is expected (--strict-markers).

Discovery Ladder

Level 0: Scope And Stack Triage (always)

Collect:

  1. Target scope (repo, package, module).
  2. Stack shape (pure Python, FastAPI, SQLAlchemy sync, SQLAlchemy async, or mixed).
  3. Speed target (what must stay instant).
  4. CI gate policy (which marker groups block merge).

If any are missing, ask concise clarifying questions before scaffolding.

Level 1: Core pytest scaffold (default)

Use this for all stacks first:

  1. Mirror src/ into tests/ with one starter file per core module.
  2. Classify test intent by cost:
    • unit: no DB/network/filesystem side effects.
    • integration: framework, DB, or multi-layer contracts.
    • smoke: thin critical-path checks.
  3. Scaffold each new module with:
    • one happy-path test,
    • one failure/edge test,
    • TODO anchors for deeper assertions.
  4. Keep fixtures layered:
    • global lightweight fixtures in tests/conftest.py,
    • domain fixtures in subtree conftest.py only when needed.
  5. Register markers early: unit, integration, smoke, slow, external.
  6. Validate in order:
    • uv run pytest --collect-only -q
    • uv run pytest -m unit -q
    • uv run pytest -q when dependencies are available.

Load next reference only if needed:

Level 2: FastAPI branch (only for HTTP/dependency/lifespan concerns)

Escalate here when testing API routes, dependency injection boundaries, or app lifespan behavior.

Apply these defaults:

  1. Prefer TestClient with sync def tests for route behavior.
  2. Use AsyncClient + @pytest.mark.anyio only when test logic must await other async work.
  3. Prefer app.dependency_overrides over patching internals.
  4. Reset dependency overrides in teardown after every test/fixture.
  5. For startup/shutdown semantics:
    • use TestClient as context manager, or
    • use LifespanManager with async client.

Marker intent in FastAPI-heavy suites:

  • unit: service logic without HTTP/DB.
  • integration: route + DI + DB contract checks.
  • smoke: one request per critical user path.

Reference: fastapi-testing.md

Level 3: SQLAlchemy branch (only for DB transaction/session design)

Escalate here when session lifecycle, transaction isolation, or async ORM behavior matters.

Apply these defaults:

  1. Create engine once per test session.
  2. Open connection + outer transaction per test.
  3. Bind session with join_transaction_mode="create_savepoint".
  4. Allow code under test to call commit() safely; rollback outer transaction at test end.
  5. Keep unit tests DB-free; DB tests belong under integration.

Async additions:

  • use async fixtures and @pytest.mark.anyio.
  • set expire_on_commit=False for AsyncSession.
  • avoid implicit lazy IO; use eager loading (selectinload) or explicit refresh.

SQLite in-memory with threaded test clients:

  • use StaticPool when required by thread/connection sharing.

Reference: sqlalchemy-testing.md

Branching Logic Summary

  • If pure logic can be faked cleanly, keep in unit.
  • If framework/DB contract is the behavior under test, use integration.
  • If external service credentials/network is required, gate behind external.
  • If suite slows down, split by marker before broadening fixture scope.
  • If async relationship access raises MissingGreenlet, switch to eager loading strategy.

Completion Checks

A scaffold pass is complete when all are true:

  1. Core source areas map to clear test modules.
  2. Fast path (-m unit) is deterministic and quick.
  3. Integration and external paths are isolated by fixtures and markers.
  4. No unregistered-marker failures occur.
  5. Structure is understandable without extra oral context.
  6. Clear TODO extension points exist for deeper assertions.

Output Contract

When this skill is applied, return:

  1. Proposed test tree diff.
  2. Marker and fixture plan.
  3. Exact fast-path and full-path commands.
  4. Which reference level was loaded and why.
  5. Risks or open questions before expanding assertions.