<!-- skill: Pytest Suite | version: 1.0.0 | description: Build pytest suites: fixtures, parametrize, markers, coverage gates, CI matrix | category: Python & Data -->
## Instructions
- Layout as `src/` + `tests/` (`test_*.py`); add `pyproject.toml [tool.pytest.ini_options]` with `testpaths=['tests']`.
- Write AAA tests (Arrange-Act-Assert): one behavior per test, plain `assert`, descriptive names (`test_checkout_rejects_empty_cart`).
- Parametrize aggressively: `@pytest.mark.parametrize('qty,expected', [...])` instead of copy-pasted test functions.
- Use fixtures for setup: `@pytest.fixture` with `yield` for teardown; scope `function` by default, `session` for DB/containers.
- Isolate I/O: `tmp_path` for files, `monkeypatch` for env/argv, `capsys/caplog` for output, `responses`/`respx` for HTTP.
- Mark intentionally: `@pytest.mark.slow`, `@pytest.mark.integration`; run fast subset with `-m 'not slow'` locally.
- Enforce coverage gates: `pytest --cov=src --cov-fail-under=85 --cov-report=term-missing`; never gate at 100% blindly.
- Run the full matrix in CI: `pytest -q`, `ruff check`, coverage upload; fail PR on red, not on warnings alone.
- Test errors explicitly: `with pytest.raises(ValueError, match='qty'): f(...)`; also assert the happy-path boundary values.
- Freeze time/randomness: `freezegun` or injected clocks, `np.random.default_rng(seed)` / `random.seed` in deterministic tests.
- Keep tests fast: no `sleep`, no network; factor slow integration tests behind markers run nightly, not per-save.
- Debug with `-x -q --tb=short -vv -k name` to iterate one failure at a time; use `--lf` (last-failed) for red-green loops.

## Examples
- Cart pricing → parametrized qty/discount table, fixture builds catalog, tmp_path asserts receipt file written.
- CSV loader → monkeypatched env for data dir, raises on missing column, coverage gate catches untested branch.
- API client → respx mocks 200/429/500, retry logic tested without network, slow live test marked integration.

## Workflows
1. Scaffold `tests/` + `pyproject.toml` pytest config + coverage settings.
2. Write failing test first (TDD for bugfixes), then minimal implementation.
3. Add fixtures/parametrize as duplication appears; mark slow/integration tests.
4. Gate locally: `pytest -q --cov=src --cov-fail-under=85`, fix misses, run `ruff`.
5. CI matrix: Python 3.10–3.13 × ubuntu/windows, upload coverage, block merge on failure.

## Templates
```python
# tests/test_pricing.py — fixtures + parametrize + tmp_path + raises
import pytest
from shop.pricing import total
@pytest.fixture
def catalog():
    return {"widget": 10.0, "gadget": 25.0}
@pytest.mark.parametrize("items,discount,expected", [
    ([("widget", 2)], 0.0, 20.0),
    ([("gadget", 1), ("widget", 1)], 0.1, 31.5),
    ([], 0.0, 0.0),
])
def test_total(catalog, items, discount, expected):
    assert total(items, catalog, discount=discount) == pytest.approx(expected)
def test_total_rejects_unknown_sku(catalog):
    with pytest.raises(KeyError, match="nope"):
        total([("nope", 1)], catalog)
def test_receipt_written(tmp_path, catalog):
    p = tmp_path / "receipt.txt"
    p.write_text(f"total={total([('widget', 1)], catalog)}")
    assert p.read_text().startswith("total=10")
```
```toml
# pyproject.toml — pytest + coverage config
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q --cov=src --cov-report=term-missing --cov-fail-under=85"
markers = ["slow: long-running tests", "integration: needs external service"]
[tool.coverage.run]
omit = ["*/tests/*", "*/__init__.py"]
```

## Tools
- Core: `pip install pytest pytest-cov pytest-mock respx freezegun`.
- Commands: `pytest -q`, `pytest -m 'not slow'`, `pytest --lf -x --tb=short`, `pytest --cov=src`.
- SARAH Run executes `pytest -q`; coverage summary prints missing lines per file.

## Advanced
- Fixture power: `params=` for matrix fixtures, `autouse=True` for env guards, `tmp_path_factory` for shared corpora.
- Mocking: `mocker.patch('shop.pay.charge', return_value={'ok': True})`; assert called once with exact payload.
- Async: `pytest-asyncio` with `asyncio_mode='auto'`; test timeouts with `pytest-timeout --timeout=5`.
- Snapshot testing: `syrupy` for golden JSON/HTML outputs; review diffs deliberately, never blind-update snapshots.
- CI matrix: `strategy.matrix python-version: ['3.10','3.11','3.12','3.13']`, cache pip, upload `coverage.xml` to Codecov.
- Troubleshooting: flaky order → no global state, seed RNG; hanging suite → `-p no:cacheprovider --timeout`; import errors → `__init__.py`/src layout + `pythonpath`.

## Documentation
- pytest guide: https://docs.pytest.org/en/stable/
- pytest-cov: https://pytest-cov.readthedocs.io/
- Good practices: https://docs.pytest.org/en/stable/explanation/goodpractices.html
