# Pytest Advanced

> When to activate: pytest plugins, conftest, custom markers, test architecture, fixtures design, pytest-xdist, coverage strategies

- Skill: `mattakushi432/pytest-advanced` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/pytest-advanced`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/pytest-advanced/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/pytest-advanced

---


# Advanced pytest Patterns

## Conftest Architecture
```
tests/
├── conftest.py              # session-scoped fixtures (db, engine, settings override)
├── unit/
│   ├── conftest.py          # unit-test-specific fixtures (no db)
│   └── test_validators.py
├── integration/
│   ├── conftest.py          # integration fixtures (real db, real redis)
│   └── test_user_flow.py
└── e2e/
    ├── conftest.py          # httpx.AsyncClient, live app
    └── test_signup.py
```

## Custom Markers
```python
# conftest.py
import pytest

def pytest_configure(config):
    config.addinivalue_line("markers", "slow: marks tests as slow (deselect with -m 'not slow')")
    config.addinivalue_line("markers", "integration: marks integration tests requiring db/redis")
    config.addinivalue_line("markers", "external: marks tests that call real external APIs")

# Usage
@pytest.mark.slow
@pytest.mark.integration
async def test_report_generation(db, client):
    ...
```

## Fixture Factories with faker
```python
from faker import Faker
import pytest

fake = Faker()

@pytest.fixture
async def make_user(db):
    created = []
    async def _make(**kwargs) -> User:
        data = {
            "email": fake.email(),
            "name": fake.name(),
            "password": "test_password_123",
            **kwargs,
        }
        user = await user_service.create(db, data)
        created.append(user)
        return user
    yield _make
    # cleanup
    for user in created:
        await db.delete(user)
    await db.commit()
```

## Parametrize with Fixtures (pytest-lazy-fixture / indirect)
```python
@pytest.mark.parametrize("role,expected_status", [
    ("admin", 200),
    ("viewer", 403),
    ("anonymous", 401),
])
async def test_admin_endpoint_access(role, expected_status, client, make_user):
    if role != "anonymous":
        user = await make_user(role=role)
        client.force_authenticate(user=user)
    
    response = await client.get("/api/admin/users")
    assert response.status_code == expected_status
```

## Parallel Test Execution
```bash
# Install pytest-xdist
pip install pytest-xdist

# Run in parallel
pytest -n auto              # auto-detect CPU count
pytest -n 4                 # 4 workers
pytest -n auto --dist=loadscope  # group by module (good for db tests)
```

## Coverage Configuration
```toml
[tool.pytest.ini_options]
addopts = ["--cov=app", "--cov-report=term-missing", "--cov-fail-under=80"]

[tool.coverage.run]
branch = true  # measure branch coverage, not just line coverage
omit = ["tests/*", "*/migrations/*", "app/main.py"]

[tool.coverage.report]
show_missing = true
skip_covered = false
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "raise NotImplementedError",
    "@(abc\\.)?abstractmethod",
]
```

## Snapshot Testing
```python
import pytest

def test_api_response_shape(snapshot, client):
    response = client.get("/api/v1/users/1")
    assert response.json() == snapshot  # generates snapshot on first run, compares on subsequent
    # Uses pytest-snapshot or syrupy
```

