Files
playbook/antigravity-awesome-skills/skills/test-guard/references/pytest.md
T
2026-07-18 00:02:59 +00:00

2.5 KiB

Test Guard — Python / pytest Patterns

Concrete applications of the nine rules for pytest projects. Read this when reviewing or writing Python tests.

Rule 2: Mock boundaries in Python

Justified mock targets:

  • HTTP clients: httpx, requests, aiohttp (or use respx / responses instead of raw mocks)
  • LLM SDK calls: openai, anthropic, litellm.completion and friends
  • Database sessions, when the database is not the subject (see Rule 9)
  • Filesystem I/O on external paths (tmp_path fixture is often better than mocking)
  • Clock and randomness: time.time, datetime.now, random (prefer freezegun or injected clocks)

Unjustified mocks (common agent-generated violations):

  • MagicMock() standing in for a Pydantic model or dataclass — construct the real thing
  • Mocking internal utility functions to isolate a "unit"
  • Mocking json.loads / json.dumps or other stdlib pure functions

Rule 3: Parametrize

# Violation: three copy-pasted tests differing by one value
def test_slug_lowercase(): ...
def test_slug_strips_spaces(): ...
def test_slug_handles_unicode(): ...

# Fix
@pytest.mark.parametrize(
    ("raw", "expected"),
    [
        ("Hello World", "hello-world"),
        ("  padded  ", "padded"),
        ("Café Menu", "cafe-menu"),
    ],
)
def test_slugify_normalizes_input(raw, expected):
    assert slugify(raw) == expected

Rule 8: Real Pydantic/dataclass instances

# Wrong — hides field typos and validation errors
state = MagicMock()
state.user_id = "123"
state.status = "ACTIVE"

# Right — Pydantic validates the construction itself
state = UserState(user_id="123", status="ACTIVE")

If a model needs many fields, add a factory fixture or use factory_boy — don't fall back to MagicMock.

Rule 9: Real database via fixtures

Use a fixture that applies real migrations (e.g., a session-scoped test database with alembic upgrade head), and function-scoped transactions rolled back per test. pytest-postgresql, testcontainers, or an SQLite-compatible fallback all work; the point is real schema, not a mocked session, whenever query or persistence logic is the subject.

pytest-specific smells

  • assert mock.call_count == N on anything internal — Rule 1 violation
  • @patch stacks three or more deep — the test is coupled to implementation; restructure
  • Asserting log output via caplog for messages no caller parses — Rule 4 violation
  • Fixtures that build mocks of project classes — Rule 8 violation, make the fixture build real objects