# Code Reference — Pytest

_21 pages_

---
# Pytest (/docs/pytest)



# Pytest [#pytest]

Python testing framework.

Browse the sidebar for every cheat sheet in this section.

## Sections [#sections]

* [Examples](./examples)


---

# Asserts (/docs/pytest/asserts)



# Asserts [#asserts]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

pytest rewrites `assert` for rich introspection — no special assert API required. Use `pytest.raises`, `approx`, and helper matchers for exceptions and floats.

## 🔧 Core concepts [#-core-concepts]

| Tool               | Use                              |
| ------------------ | -------------------------------- |
| `assert`           | Equality, membership, truthiness |
| `pytest.raises`    | Expect exceptions                |
| `pytest.approx`    | Float tolerance                  |
| `pytest.warns`     | Expect warnings                  |
| `unittest` asserts | Still work if preferred          |

Assertion rewriting applies to test modules; helper modules may need `pytest.register_assert_rewrite`.

## 💡 Examples [#-examples]

**Basics:**

```python
def test_list():
    data = [1, 2, 3]
    assert data == [1, 2, 3]
    assert 2 in data
    assert len(data) == 3
```

**Exceptions:**

```python
import pytest

def test_raises():
    with pytest.raises(ZeroDivisionError):
        1 / 0

def test_message():
    with pytest.raises(ValueError, match="invalid"):
        raise ValueError("invalid id")
```

**Approx & warnings:**

```python
assert 0.1 + 0.2 == pytest.approx(0.3)
assert [0.1, 0.2] == pytest.approx([0.1, 0.2])

with pytest.warns(UserWarning, match="deprecated"):
    warn_user()
```

**Failure messages:**

```python
assert result.ok, f"expected ok, got {result!r}"
```

## ⚠️ Pitfalls [#️-pitfalls]

* Writing `assertEqual` from unittest without need — plain assert is fine.
* Comparing floats with `==` without `approx`.
* Catching exceptions with try/except instead of `pytest.raises`.
* Huge diffs from comparing large dicts — use focused keys.
* Assert rewriting disabled in some imported helpers.

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [Parametrize](/docs/pytest/parametrize)
* [Mocking](/docs/pytest/mocking)
* [capsys & caplog](/docs/pytest/capsys-caplog)
* [CLI](/docs/pytest/cli)
* [Coverage](/docs/pytest/coverage)


---

# Asyncio (/docs/pytest/asyncio-pytest)



# Asyncio [#asyncio]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`pytest-asyncio` runs `async def` tests and fixtures. Modern default is `asyncio_mode = auto` so marked or auto-detected coroutines work cleanly.

## 🔧 Core concepts [#-core-concepts]

| Piece                 | Role                                 |
| --------------------- | ------------------------------------ |
| `pytest.mark.asyncio` | Mark async test (strict mode)        |
| `asyncio_mode`        | `strict` \| `auto`                   |
| Async fixtures        | `async def` + await setup            |
| `event_loop`          | Legacy loop fixture (avoid new code) |
| `AsyncMock`           | Mock awaitables                      |

Prefer one event loop policy per process; be careful with xdist.

## 💡 Examples [#-examples]

**Install & config:**

```shellscript
python -m pip install pytest-asyncio
```

```toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
```

**Async test:**

```python
import pytest
import httpx

@pytest.mark.asyncio
async def test_ping():
    async with httpx.AsyncClient() as client:
        r = await client.get("https://example.com")
    assert r.status_code == 200
```

With `asyncio_mode = auto`, the mark is optional for async tests.

**Async fixture:**

```python
@pytest.fixture
async def db_pool():
    pool = await create_pool()
    yield pool
    await pool.close()

async def test_row(db_pool):
    assert await db_pool.fetchval("SELECT 1") == 1
```

**Mock async:**

```python
from unittest.mock import AsyncMock

async def test_service():
    fetch = AsyncMock(return_value={"id": 1})
    assert await fetch() == {"id": 1}
```

## ⚠️ Pitfalls [#️-pitfalls]

* Mixing sync fixtures that block the loop heavily.
* Forgetting `await` on coroutine mocks → always-true assertions.
* Session-scoped async fixtures with function-scoped loops.
* Sharing aiohttp/httpx clients across tests without reset.
* Nested event loops (`asyncio.run` inside async tests).

## 🔗 Related [#-related]

* [Install](/docs/pytest/install)
* [Fixtures](/docs/pytest/fixtures)
* [Mocking](/docs/pytest/mocking)
* [Plugins](/docs/pytest/plugins)
* [Configuration](/docs/pytest/configuration)
* [xdist](/docs/pytest/xdist)


---

# capsys & caplog (/docs/pytest/capsys-caplog)



# capsys & caplog [#capsys--caplog]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`capsys` / `capfd` capture stdout/stderr; `caplog` captures logging records. Use them to assert CLI output and log messages without polluting the terminal.

## 🔧 Core concepts [#-core-concepts]

| Fixture           | Captures                |
| ----------------- | ----------------------- |
| `capsys`          | sys.stdout / sys.stderr |
| `capfd`           | OS-level fd 1/2         |
| `capsysbinary`    | Binary stdout/stderr    |
| `caplog`          | logging module records  |
| `caplog.at_level` | Temporary level         |

`readouterr()` returns a named tuple `(out, err)` and clears buffers.

## 💡 Examples [#-examples]

**stdout:**

```python
def test_cli(capsys):
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
```

**Disable capture temporarily:**

```python
def test_debug(capsys):
    with capsys.disabled():
        print("visible during test")
```

**Logging:**

```python
import logging

def test_log(caplog):
    logger = logging.getLogger("app")
    with caplog.at_level(logging.INFO, logger="app"):
        logger.info("started")
        logger.error("boom")
    assert "started" in caplog.text
    assert any(r.levelname == "ERROR" for r in caplog.records)
```

**capfd for C extensions / subprocess writing to fd:**

```python
def test_fd(capfd):
    import os
    os.write(1, b"raw\n")
    assert "raw" in capfd.readouterr().out
```

## ⚠️ Pitfalls [#️-pitfalls]

* Forgetting `readouterr()` clears — second call is empty.
* Using `capsys` when the code writes via OS fds — use `capfd`.
* Asserting on log format strings instead of message/record fields.
* Leaving logger level too high and capturing nothing.
* Relying on capture order with concurrent threads.

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [Asserts](/docs/pytest/asserts)
* [CLI](/docs/pytest/cli)
* [Mocking](/docs/pytest/mocking)
* [tmp\_path](/docs/pytest/tmp-path)
* [conftest](/docs/pytest/conftest)


---

# CLI (/docs/pytest/cli)



# CLI [#cli]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

The pytest CLI selects tests, controls verbosity, stops on failure, and passes plugin options. Learn a small set of flags for daily use and CI.

## 🔧 Core concepts [#-core-concepts]

| Flag            | Purpose                    |
| --------------- | -------------------------- |
| `-q` / `-v`     | Quiet / verbose            |
| `-x`            | Stop on first failure      |
| `--lf` / `--ff` | Last failed / failed first |
| `-k EXPR`       | Name expression            |
| `-m EXPR`       | Mark expression            |
| `-s`            | No capture (show print)    |
| `--pdb`         | Drop into debugger         |
| `--maxfail=N`   | Stop after N failures      |
| `-n`            | xdist workers              |

Node id: `path/test_file.py::TestClass::test_name`.

## 💡 Examples [#-examples]

**Selection:**

```shellscript
pytest tests/test_api.py
pytest tests/test_api.py::test_health
pytest -k "user and not slow"
pytest -m "not integration"
```

**Debugging:**

```shellscript
pytest -x --pdb
pytest --lf
pytest -vv -s
pytest --trace   # break at start of each test
```

**Output & reports:**

```shellscript
pytest -ra
pytest --tb=short
pytest --junitxml=report.xml
```

**Useful combos:**

```shellscript
pytest -q --cov=myapp --cov-report=term-missing
pytest -n auto -m "not slow"
pytest --durations=10
```

**Collect only:**

```shellscript
pytest --collect-only -q
```

## ⚠️ Pitfalls [#️-pitfalls]

* `-s` flooding CI logs.
* Overusing `-k` strings that silently match nothing useful.
* Forgetting quotes around mark expressions in shells.
* Running from the wrong cwd so `testpaths` miss files.
* Passing pytest args after `--` incorrectly in some wrappers.

## 🔗 Related [#-related]

* [Configuration](/docs/pytest/configuration)
* [Markers](/docs/pytest/markers)
* [Coverage](/docs/pytest/coverage)
* [xdist](/docs/pytest/xdist)
* [Install](/docs/pytest/install)
* [Asserts](/docs/pytest/asserts)


---

# Configuration (/docs/pytest/configuration)



# Configuration [#configuration]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Configure pytest in `pytest.ini`, `pyproject.toml`, or `tox.ini`. Prefer `[tool.pytest.ini_options]` in modern projects; keep discovery and defaults versioned.

## 🔧 Core concepts [#-core-concepts]

| Key                                        | Role                     |
| ------------------------------------------ | ------------------------ |
| `testpaths`                                | Where to look            |
| `python_files` / `_classes` / `_functions` | Discovery patterns       |
| `addopts`                                  | Default CLI flags        |
| `markers`                                  | Register marks           |
| `filterwarnings`                           | Warning policy           |
| `minversion`                               | Require pytest version   |
| `pythonpath`                               | Import roots (pytest 7+) |

INI values are strings; TOML can use lists.

## 💡 Examples [#-examples]

**pyproject.toml:**

```toml
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
pythonpath = ["src"]
addopts = [
  "-ra",
  "--strict-markers",
  "--strict-config",
]
markers = [
  "slow: long tests",
  "integration: needs services",
]
filterwarnings = [
  "error",
  "ignore::DeprecationWarning:legacy_pkg.*",
]
```

**pytest.ini equivalent:**

```ini
[pytest]
testpaths = tests
addopts = -ra --strict-markers
markers =
    slow: long tests
```

**Env:**

```shellscript
export PYTEST_ADDOPTS="-q --tb=short"
```

**Per-directory:** nested `conftest.py` + local `pytest.ini` (rare; prefer one root config).

## ⚠️ Pitfalls [#️-pitfalls]

* Duplicate config in both `pytest.ini` and `pyproject.toml` — one source of truth.
* `addopts` including `--cov` making bare `pytest` slow for quick runs.
* Forgetting `--strict-markers` then typos silently create new marks.
* Wrong `pythonpath` masking packaging issues.
* Setting `norecursedirs` too aggressively and skipping test folders.

## 🔗 Related [#-related]

* [Install](/docs/pytest/install)
* [CLI](/docs/pytest/cli)
* [Markers](/docs/pytest/markers)
* [conftest](/docs/pytest/conftest)
* [Plugins](/docs/pytest/plugins)
* [Coverage](/docs/pytest/coverage)


---

# conftest (/docs/pytest/conftest)



# conftest [#conftest]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`conftest.py` holds shared fixtures, hooks, and plugins discovered by directory proximity. Nested conftests apply to their subtree; no import needed in tests.

## 🔧 Core concepts [#-core-concepts]

| Feature          | Role                                                |
| ---------------- | --------------------------------------------------- |
| Local fixtures   | Visible to sibling/child tests                      |
| Hooks            | `pytest_configure`, `pytest_collection_modifyitems` |
| `pytest_plugins` | Load plugin modules                                 |
| Directory scope  | Closest conftest wins for overrides                 |

Do not put `conftest.py` in importable app packages if it confuses packaging.

## 💡 Examples [#-examples]

**Shared fixture:**

```python
# tests/conftest.py
import pytest

@pytest.fixture
def client(app):
    return app.test_client()
```

**Hook — skip by mark in CI:**

```python
# tests/conftest.py
import os
import pytest

def pytest_collection_modifyitems(config, items):
    if os.getenv("CI"):
        return
    skip_ci = pytest.mark.skip(reason="CI only")
    for item in items:
        if "ci_only" in item.keywords:
            item.add_marker(skip_ci)
```

**Nested override:**

```plaintext
tests/
  conftest.py          # db fixture (sqlite)
  api/
    conftest.py        # db fixture (postgres) overrides for api/
    test_users.py
```

**Register plugin:**

```python
pytest_plugins = ["tests.plugins.db"]
```

## ⚠️ Pitfalls [#️-pitfalls]

* Circular imports between conftest and application code.
* Putting secrets in conftest committed to git.
* Too many session fixtures in root conftest slowing collection.
* Expecting conftest fixtures across unrelated directories.
* Naming conflicts when two conftests define the same fixture name.

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [Plugins](/docs/pytest/plugins)
* [Configuration](/docs/pytest/configuration)
* [Markers](/docs/pytest/markers)
* [tmp\_path](/docs/pytest/tmp-path)
* [Django pytest](/docs/pytest/django-pytest)


---

# Coverage (/docs/pytest/coverage)



# Coverage [#coverage]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`pytest-cov` integrates coverage.py: measure line/branch coverage, fail under thresholds, and emit terminal/HTML/XML reports for CI.

## 🔧 Core concepts [#-core-concepts]

| Flag                 | Meaning                |
| -------------------- | ---------------------- |
| `--cov=pkg`          | Measure package        |
| `--cov-report=`      | term, html, xml        |
| `--cov-fail-under=N` | Exit non-zero if below |
| `--cov-branch`       | Branch coverage        |
| `omit` / `include`   | Scope in `.coveragerc` |

Run from project root so paths resolve correctly.

## 💡 Examples [#-examples]

**CLI:**

```shellscript
pytest --cov=myapp --cov-report=term-missing
pytest --cov=myapp --cov-report=html --cov-report=xml
pytest --cov=myapp --cov-fail-under=85 --cov-branch
```

**pyproject.toml:**

```toml
[tool.coverage.run]
source = ["myapp"]
branch = true
omit = ["*/tests/*", "*/migrations/*"]

[tool.coverage.report]
show_missing = true
skip_covered = false
fail_under = 85

[tool.pytest.ini_options]
addopts = "--cov=myapp --cov-report=term-missing"
```

**HTML:** open `htmlcov/index.html`.

**CI:** publish `coverage.xml` to Codecov/Sonar; keep thresholds realistic for new projects.

## ⚠️ Pitfalls [#️-pitfalls]

* Measuring tests or virtualenv paths — set `source`/`omit`.
* 100% line coverage with zero meaningful assertions.
* Combining xdist without `pytest-cov` concurrency support configured.
* Dynamic imports / `# pragma: no cover` abused to hide gaps.
* Different coverage between local and CI due to optional extras.

## 🔗 Related [#-related]

* [Install](/docs/pytest/install)
* [CLI](/docs/pytest/cli)
* [Configuration](/docs/pytest/configuration)
* [xdist](/docs/pytest/xdist)
* [Plugins](/docs/pytest/plugins)
* [Asserts](/docs/pytest/asserts)


---

# Django pytest (/docs/pytest/django-pytest)



# Django pytest [#django-pytest]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`pytest-django` replaces `manage.py test` with pytest fixtures: `client`, `admin_client`, `db`, `django_user_model`, and DB access markers.

## 🔧 Core concepts [#-core-concepts]

| Fixture / mark     | Role               |
| ------------------ | ------------------ |
| `db` / `django_db` | Enable DB access   |
| `client`           | Django test client |
| `admin_client`     | Logged-in staff    |
| `rf`               | RequestFactory     |
| `settings`         | Override settings  |
| `live_server`      | Live HTTP server   |

Configure `DJANGO_SETTINGS_MODULE` in pytest config or env.

## 💡 Examples [#-examples]

**Install:**

```shellscript
python -m pip install pytest-django
```

```toml
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "myproject.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"]
```

**View test:**

```python
import pytest

@pytest.mark.django_db
def test_home(client):
    res = client.get("/")
    assert res.status_code == 200
```

**Model + user:**

```python
@pytest.mark.django_db
def test_user(django_user_model):
    u = django_user_model.objects.create_user("ada", password="x")
    assert u.username == "ada"
```

**Settings override:**

```python
def test_debug(settings):
    settings.DEBUG = True
    assert settings.DEBUG is True
```

**Transactional / live:**

```python
@pytest.mark.django_db(transaction=True)
def test_signal():
    ...

@pytest.mark.django_db
def test_api(live_server, client):
    assert live_server.url.startswith("http")
```

## ⚠️ Pitfalls [#️-pitfalls]

* Accessing DB without `django_db` / `db` fixture → error.
* Relying on migration state without `pytest-django` DB setup.
* Using unittest `TestCase` transactions mixed with pytest fixtures carelessly.
* Factory Boy / fixtures creating data without cleanup in non-db tests.
* Pointing `DJANGO_SETTINGS_MODULE` at production settings.

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [Markers](/docs/pytest/markers)
* [Configuration](/docs/pytest/configuration)
* [Plugins](/docs/pytest/plugins)
* [Mocking](/docs/pytest/mocking)
* [Install](/docs/pytest/install)


---

# Examples (/docs/pytest/examples)



# Examples [#examples]

Pytest notes in **Examples**.


---

# Parametrize IDs (/docs/pytest/examples/parametrize-ids)



# Parametrize IDs [#parametrize-ids]

*Pytest · Example / how-to*

***

## 📋 Overview [#-overview]

Use `@pytest.mark.parametrize` with explicit `ids=` so failure output names cases clearly.

## 🔧 Core concepts [#-core-concepts]

| Piece          | Role                      |
| -------------- | ------------------------- |
| `parametrize`  | Run one test many ways    |
| `ids`          | Human-readable case names |
| `pytest.param` | Per-case marks / id       |
| Failure output | Shows id in nodeid        |

## 💡 Examples [#-examples]

**test\_parametrize\_ids.py:**

```python
import pytest


@pytest.mark.parametrize(
    ("raw", "expected"),
    [
        ("  ada  ", "ada"),
        ("ADA", "ada"),
        ("", ""),
    ],
    ids=["trim", "lower", "empty"],
)
def test_normalize(raw: str, expected: str) -> None:
    assert raw.strip().lower() == expected


@pytest.mark.parametrize(
    "value",
    [
        pytest.param(2, id="even"),
        pytest.param(3, id="odd"),
        pytest.param(-1, id="negative", marks=pytest.mark.xfail(reason="not yet")),
    ],
)
def test_is_non_negative_even(value: int) -> None:
    assert value >= 0 and value % 2 == 0
```

**Run:**

```shellscript
pytest -q test_parametrize_ids.py
pytest -q test_parametrize_ids.py -k trim
```

## ⚠️ Pitfalls [#️-pitfalls]

* Auto ids from values can be ugly or collide — set `ids` for clarity.
* Too many combinations explode runtime — prefer focused tables.
* Marks on `pytest.param` apply only to that case, not the whole matrix.

## 🔗 Related [#-related]

* [tmp\_path files](/docs/pytest/examples/tmp-path-files)


---

# tmp_path Files (/docs/pytest/examples/tmp-path-files)



# tmp\_path Files [#tmp_path-files]

*Pytest · Example / how-to*

***

## 📋 Overview [#-overview]

Use the `tmp_path` fixture to create isolated files/directories per test without polluting the repo.

## 🔧 Core concepts [#-core-concepts]

| Piece              | Role                           |
| ------------------ | ------------------------------ |
| `tmp_path`         | Unique `pathlib.Path` per test |
| Write / read       | Exercise file IO safely        |
| Cleanup            | Pytest removes the dir         |
| `tmp_path_factory` | Session-scoped temps           |

## 💡 Examples [#-examples]

**test\_tmp\_path\_files.py:**

```python
from pathlib import Path


def test_write_and_read_config(tmp_path: Path) -> None:
    config = tmp_path / "config.ini"
    config.write_text("[app]\ndebug = true\n", encoding="utf-8")

    text = config.read_text(encoding="utf-8")
    assert "debug = true" in text


def test_nested_layout(tmp_path: Path) -> None:
    inbox = tmp_path / "inbox"
    inbox.mkdir()
    sample = inbox / "note.md"
    sample.write_text("# hi\n", encoding="utf-8")

    files = sorted(p.name for p in inbox.iterdir())
    assert files == ["note.md"]


def test_factory_shared_seed(tmp_path_factory) -> None:
    root = tmp_path_factory.mktemp("seed")
    (root / "data.csv").write_text("a,b\n1,2\n", encoding="utf-8")
    assert (root / "data.csv").exists()
```

## ⚠️ Pitfalls [#️-pitfalls]

* Do not hard-code `/tmp/...` paths — use the fixture for isolation.
* Binary vs text: pass `encoding` for text; use `write_bytes` for binaries.
* Tests must not assume leftover files from a previous run.

## 🔗 Related [#-related]

* [Parametrize ids](/docs/pytest/examples/parametrize-ids)


---

# Fixtures (/docs/pytest/fixtures)



# Fixtures [#fixtures]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Fixtures provide setup/teardown via dependency injection. Tests request fixtures by parameter name; scopes control lifetime (`function`, `class`, `module`, `package`, `session`).

## 🔧 Core concepts [#-core-concepts]

| Concept           | Meaning                      |
| ----------------- | ---------------------------- |
| `@pytest.fixture` | Define reusable resource     |
| `yield`           | Setup before, teardown after |
| `scope`           | How often to recreate        |
| `autouse`         | Apply without requesting     |
| `params`          | Parametrized fixture         |
| Fixture factory   | Return a creator callable    |

Prefer fixtures over setup/teardown methods for composability.

## 💡 Examples [#-examples]

**Basic yield fixture:**

```python
import pytest

@pytest.fixture
def db():
    conn = connect()
    yield conn
    conn.close()

def test_query(db):
    assert db.execute("SELECT 1").fetchone()[0] == 1
```

**Scopes:**

```python
@pytest.fixture(scope="session")
def app():
    return create_app()

@pytest.fixture(scope="module")
def client(app):
    return app.test_client()
```

**Factory:**

```python
@pytest.fixture
def make_user(db):
    created = []
    def _make(**kwargs):
        u = User(**kwargs)
        db.add(u)
        created.append(u)
        return u
    yield _make
    for u in created:
        db.delete(u)
```

**Autouse:**

```python
@pytest.fixture(autouse=True)
def _fast_cache(monkeypatch):
    monkeypatch.setenv("CACHE_URL", "memory://")
```

## ⚠️ Pitfalls [#️-pitfalls]

* Session-scoped mutable state leaking between tests.
* Requesting heavy fixtures unnecessarily — keep scopes tight.
* Using `return` when teardown is needed — use `yield`.
* Circular fixture dependencies.
* Overusing `autouse` — hard to see what a test depends on.

## 🔗 Related [#-related]

* [conftest](/docs/pytest/conftest)
* [Parametrize](/docs/pytest/parametrize)
* [tmp\_path](/docs/pytest/tmp-path)
* [capsys & caplog](/docs/pytest/capsys-caplog)
* [Mocking](/docs/pytest/mocking)
* [Markers](/docs/pytest/markers)


---

# Glossary (/docs/pytest/glossary)



# Glossary [#glossary]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Alphabetical glossary of core Pytest terms for fixtures, assertions, markers, configuration, and test organization.

## 🔧 Core concepts [#-core-concepts]

| Term             | Definition                                                                        |
| ---------------- | --------------------------------------------------------------------------------- |
| Approx           | `pytest.approx` for comparing floating-point numbers with tolerance.              |
| Assert rewriting | Pytest’s enhancement of `assert` statements with rich failure introspection.      |
| Caplog           | A fixture that captures logging output during a test.                             |
| Capsys           | A fixture that captures writes to stdout and stderr.                              |
| Collection       | The phase where Pytest discovers and builds the test suite tree.                  |
| Conftest         | A `conftest.py` file that shares fixtures and hooks with a directory tree.        |
| Coverage         | Measurement of which code lines were executed by tests.                           |
| Fixture          | A reusable setup/teardown provider injected into tests by name.                   |
| Hook             | A plugin callback such as `pytest_runtest_setup` that extends Pytest.             |
| Marker           | A tag like `@pytest.mark.slow` used to select or skip tests.                      |
| Monkeypatch      | A fixture for temporarily changing attributes, env vars, or dict items.           |
| Node id          | The unique path identifying a collected test, used in selection.                  |
| Parametrize      | Running one test function multiple times with different argument sets.            |
| Plugin           | An extension package or module that adds fixtures, hooks, or CLI options.         |
| Pytest.ini       | A common configuration file for options, markers, and paths.                      |
| Raise            | Using `pytest.raises` to assert that code throws an expected exception.           |
| Request          | The fixture that gives access to the current test context and node.               |
| Scope            | How long a fixture lives: `function`, `class`, `module`, `package`, or `session`. |
| Skip             | Marking a test to not run, optionally with a reason.                              |
| Teardown         | Cleanup code that runs after a test or fixture yields.                            |
| Tmp\_path        | A fixture providing a unique temporary `pathlib.Path` per test.                   |
| Traceback        | The failure stack Pytest prints, often shortened for readability.                 |
| Usefixtures      | A marker that applies fixtures to a test or class without parameters.             |
| Xdist            | The `pytest-xdist` plugin that runs tests in parallel workers.                    |
| Xfail            | Expecting a test to fail; unexpected passes are reported specially.               |

## 💡 Examples [#-examples]

**Fixture and assert:**

```python
import pytest

@pytest.fixture
def user():
    return {"name": "Ada"}

def test_user_name(user):
    assert user["name"] == "Ada"
```

**Parametrize and markers:**

```python
@pytest.mark.parametrize("n,expected", [(1, 2), (2, 4)])
def test_double(n, expected):
    assert n * 2 == expected

@pytest.mark.slow
def test_integration():
    ...
```

**CLI selection:**

```shellscript
pytest -k "user and not slow" -q
pytest -m "not slow" --maxfail=1
```

## ⚠️ Pitfalls [#️-pitfalls]

* Confusing **skip** (do not run) with **xfail** (run, expect failure).
* Mixing **fixture scope** (`function` vs `session`) and accidentally sharing mutable state.
* Treating **conftest.py** like a normal importable module — discovery is directory-based.
* Equating **parametrize** ids with test names — node ids include parameters.
* Assuming plain **assert** elsewhere behaves like Pytest’s rewritten asserts.

## 🔗 Related [#-related]

* [fixtures](/docs/pytest/fixtures)
* [asserts](/docs/pytest/asserts)
* [parametrize](/docs/pytest/parametrize)
* [markers](/docs/pytest/markers)
* [conftest](/docs/pytest/conftest)
* [cli](/docs/pytest/cli)


---

# Install (/docs/pytest/install)



# Install [#install]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

pytest is the standard Python test runner: discovery, fixtures, asserts, plugins. Install into a venv; optional extras cover coverage, Django, asyncio, and xdist.

## 🔧 Core concepts [#-core-concepts]

| Package          | Role               |
| ---------------- | ------------------ |
| `pytest`         | Core runner        |
| `pytest-cov`     | Coverage           |
| `pytest-xdist`   | Parallel           |
| `pytest-asyncio` | Async tests        |
| `pytest-django`  | Django integration |

Discovery: `test_*.py`, `*_test.py`, `Test*` classes, `test_*` functions.

## 💡 Examples [#-examples]

**Install:**

```shellscript
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
python -m pip install -U pip
python -m pip install pytest pytest-cov
```

**Minimal test:**

```python
# tests/test_math.py
def test_add():
    assert 1 + 1 == 2
```

**Run:**

```shellscript
pytest
pytest -q
pytest tests/test_math.py::test_add
pytest -k "add and not slow"
```

**pyproject.toml:**

```toml
[project.optional-dependencies]
dev = ["pytest>=8.0", "pytest-cov>=5.0"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
addopts = "-ra"
```

## ⚠️ Pitfalls [#️-pitfalls]

* Running system `pytest` outside the venv → wrong packages.
* Putting tests next to app without `pythonpath` / src layout.
* Naming files `test.py` only — discovery still works but prefer packages.
* Mixing unittest discovery expectations with pytest layout.
* Forgetting `__init__.py` when import paths need packages (often optional).

## 🔗 Related [#-related]

* [Configuration](/docs/pytest/configuration)
* [CLI](/docs/pytest/cli)
* [Fixtures](/docs/pytest/fixtures)
* [Plugins](/docs/pytest/plugins)
* [Coverage](/docs/pytest/coverage)
* [xdist](/docs/pytest/xdist)


---

# Markers (/docs/pytest/markers)



# Markers [#markers]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Markers tag tests for selection, skipping, expected failure, and custom metadata. Register custom marks in config to avoid warnings.

## 🔧 Core concepts [#-core-concepts]

| Built-in          | Effect          |
| ----------------- | --------------- |
| `skip` / `skipif` | Do not run      |
| `xfail`           | Expected fail   |
| `parametrize`     | Multiple cases  |
| `usefixtures`     | Attach fixtures |
| `filterwarnings`  | Warning filters |

Custom: `@pytest.mark.slow`, `@pytest.mark.integration`, etc.

## 💡 Examples [#-examples]

**Skip / xfail:**

```python
import sys
import pytest

@pytest.mark.skip(reason="not ready")
def test_wip():
    ...

@pytest.mark.skipif(sys.platform == "win32", reason="posix only")
def test_fcntl():
    ...

@pytest.mark.xfail(reason="bug #123", strict=True)
def test_known_bug():
    assert False
```

**Custom marks + selection:**

```python
@pytest.mark.slow
def test_big_import():
    ...

# pytest -m "not slow"
# pytest -m "integration and not slow"
```

**Register in pyproject.toml:**

```toml
[tool.pytest.ini_options]
markers = [
  "slow: long-running tests",
  "integration: needs services",
]
```

**Class-level:**

```python
pytestmark = pytest.mark.integration

class TestAPI:
    def test_health(self):
        ...
```

## ⚠️ Pitfalls [#️-pitfalls]

* Unregistered marks → `PytestUnknownMarkWarning`.
* `xfail` without `strict=True` hiding unexpectedly passing tests.
* Overlapping mark expressions that select nothing silently.
* Using skip for environment issues better handled by fixtures.
* Marking entire modules accidentally via `pytestmark`.

## 🔗 Related [#-related]

* [CLI](/docs/pytest/cli)
* [Parametrize](/docs/pytest/parametrize)
* [Configuration](/docs/pytest/configuration)
* [Fixtures](/docs/pytest/fixtures)
* [Asserts](/docs/pytest/asserts)
* [Django pytest](/docs/pytest/django-pytest)


---

# Mocking (/docs/pytest/mocking)



# Mocking [#mocking]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Isolate units with `monkeypatch`, `unittest.mock`, or `pytest-mock`'s `mocker`. Patch where the name is looked up, not where it is defined.

## 🔧 Core concepts [#-core-concepts]

| Tool                  | Strength                      |
| --------------------- | ----------------------------- |
| `monkeypatch`         | Built-in, env/attrs/dicts     |
| `mocker`              | Convenient MagickMock helpers |
| `unittest.mock`       | patch, Mock, AsyncMock        |
| `respx` / `responses` | HTTP mocking                  |
| `freezegun`           | Time                          |

Prefer fakes/fakes over mocks when behavior matters.

## 💡 Examples [#-examples]

**monkeypatch:**

```python
def test_home(monkeypatch, tmp_path):
    monkeypatch.setenv("HOME", str(tmp_path))
    monkeypatch.setattr("app.service.fetch", lambda: {"ok": True})
    monkeypatch.delenv("SECRET", raising=False)
```

**pytest-mock:**

```python
def test_send(mocker):
    send = mocker.patch("app.mail.send_email", return_value=True)
    assert do_invite("a@b.com") is True
    send.assert_called_once_with("a@b.com")
```

**unittest.mock:**

```python
from unittest.mock import patch, MagicMock

@patch("app.db.Session")
def test_query(Session):
    Session.return_value.query.return_value = []
    assert list_users() == []
```

**Async:**

```python
async def test_async(mocker):
    mocker.patch("app.api.fetch", new_callable=mocker.AsyncMock, return_value=1)
```

## ⚠️ Pitfalls [#️-pitfalls]

* Patching the definition site instead of the import site.
* Over-mocking until the test asserts only the mock.
* Leaving patches active without context managers / fixtures.
* Mocking timestamps without also freezing related clocks.
* Asserting call order when concurrency makes it nondeterministic.

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [Asserts](/docs/pytest/asserts)
* [asyncio](/docs/pytest/asyncio-pytest)
* [Plugins](/docs/pytest/plugins)
* [tmp\_path](/docs/pytest/tmp-path)
* [capsys & caplog](/docs/pytest/capsys-caplog)


---

# Parametrize (/docs/pytest/parametrize)



# Parametrize [#parametrize]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`@pytest.mark.parametrize` runs one test body with many inputs. Combine with fixture params and `pytest.param` for ids, marks, and expected exceptions.

## 🔧 Core concepts [#-core-concepts]

| Form                        | Use                |
| --------------------------- | ------------------ |
| `@pytest.mark.parametrize`  | Table-driven tests |
| `ids=`                      | Readable node ids  |
| `pytest.param(..., marks=)` | Per-row marks      |
| Indirect parametrization    | Feed fixtures      |
| Stacked parametrize         | Cartesian product  |

## 💡 Examples [#-examples]

**Simple table:**

```python
import pytest

@pytest.mark.parametrize(
    "a,b,expected",
    [
        (1, 1, 2),
        (2, 3, 5),
        (-1, 1, 0),
    ],
)
def test_add(a, b, expected):
    assert a + b == expected
```

**Ids & marks:**

```python
@pytest.mark.parametrize(
    "value",
    [
        pytest.param(1, id="one"),
        pytest.param(0, marks=pytest.mark.xfail(reason="zero")),
        pytest.param(-1, marks=pytest.mark.skip(reason="neg")),
    ],
)
def test_value(value):
    assert value > 0
```

**Indirect:**

```python
@pytest.fixture
def user(request):
    return User(role=request.param)

@pytest.mark.parametrize("user", ["admin", "guest"], indirect=True)
def test_access(user):
    assert user.role in {"admin", "guest"}
```

**Cartesian:**

```python
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", ["a", "b"])
def test_grid(x, y):
    assert f"{x}{y}"
```

## ⚠️ Pitfalls [#️-pitfalls]

* Huge cartesian products exploding CI time.
* Unhashable / huge objects as params — use factories/ids.
* Duplicate ids causing confusion in reports.
* Putting setup logic in params instead of fixtures.
* Over-parametrizing when a property-based test fits better.

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [Markers](/docs/pytest/markers)
* [Asserts](/docs/pytest/asserts)
* [CLI](/docs/pytest/cli)
* [conftest](/docs/pytest/conftest)
* [xdist](/docs/pytest/xdist)


---

# Plugins (/docs/pytest/plugins)



# Plugins [#plugins]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Plugins extend pytest via entry points or local modules. Popular ones cover coverage, Django, asyncio, timeouts, random order, and HTML reports.

## 🔧 Core concepts [#-core-concepts]

| Plugin           | Purpose          |
| ---------------- | ---------------- |
| `pytest-cov`     | Coverage         |
| `pytest-xdist`   | Parallel         |
| `pytest-asyncio` | Async            |
| `pytest-django`  | Django           |
| `pytest-timeout` | Hang protection  |
| `pytest-mock`    | `mocker` fixture |
| `pytest-html`    | HTML report      |

Local plugins: modules with hooks, loaded via `pytest_plugins` or `-p`.

## 💡 Examples [#-examples]

**Install common set:**

```shellscript
python -m pip install pytest-cov pytest-xdist pytest-asyncio pytest-mock
```

**Minimal local plugin:**

```python
# tests/plugins/env.py
def pytest_addoption(parser):
    parser.addoption("--runslow", action="store_true")

def pytest_configure(config):
    config.addinivalue_line("markers", "slow: slow tests")
```

```python
# conftest.py
pytest_plugins = ["tests.plugins.env"]
```

**List plugins:**

```shellscript
pytest --trace-config
pytest -p no:cacheprovider
```

**Entry-point plugin sketch:**

```python
# myplugin.py
def pytest_runtest_setup(item):
    if "network" in item.keywords and not item.config.getoption("--network"):
        pytest.skip("needs --network")
```

## ⚠️ Pitfalls [#️-pitfalls]

* Plugin version skew with pytest major upgrades.
* Loading the same plugin twice (entry point + pytest\_plugins).
* Order-dependent plugins mutating collection unexpectedly.
* Disabling builtin plugins accidentally with `-p no:...`.
* Heavy plugins imported at collection time slowing startup.

## 🔗 Related [#-related]

* [Install](/docs/pytest/install)
* [conftest](/docs/pytest/conftest)
* [Coverage](/docs/pytest/coverage)
* [xdist](/docs/pytest/xdist)
* [asyncio](/docs/pytest/asyncio-pytest)
* [Mocking](/docs/pytest/mocking)


---

# tmp_path (/docs/pytest/tmp-path)



# tmp\_path [#tmp_path]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`tmp_path` and `tmp_path_factory` give per-test (or session) temporary directories as `pathlib.Path`. Prefer them over manual `tempfile` cleanup.

## 🔧 Core concepts [#-core-concepts]

| Fixture            | Scope                               |
| ------------------ | ----------------------------------- |
| `tmp_path`         | Unique dir per test function        |
| `tmp_path_factory` | Create extra dirs; session-friendly |
| `tmpdir`           | Legacy py.path — prefer `tmp_path`  |
| basetemp           | `--basetemp=` keeps dirs for debug  |

pytest cleans default temp roots between runs unless basetemp is set.

## 💡 Examples [#-examples]

**Write files:**

```python
def test_config(tmp_path):
    cfg = tmp_path / "config.toml"
    cfg.write_text('[app]\nname = "demo"\n', encoding="utf-8")
    assert "demo" in cfg.read_text(encoding="utf-8")
```

**Nested layout:**

```python
def test_project(tmp_path):
    src = tmp_path / "src"
    src.mkdir()
    (src / "main.py").write_text("print(1)\n", encoding="utf-8")
    assert (tmp_path / "src" / "main.py").exists()
```

**Factory (session):**

```python
import pytest

@pytest.fixture(scope="session")
def dataset_dir(tmp_path_factory):
    root = tmp_path_factory.mktemp("data")
    (root / "sample.csv").write_text("a,b\n1,2\n", encoding="utf-8")
    return root
```

**Keep on failure:**

```shellscript
pytest --basetemp=./.pytest-tmp
```

## ⚠️ Pitfalls [#️-pitfalls]

* Storing secrets in tmp dirs on shared CI runners.
* Assuming `tmp_path` survives across tests — it does not.
* Using string paths with APIs that need `Path` or vice versa.
* Parallel xdist workers — each has isolated temps; don't hardcode shared paths.
* Preferring `tmpdir` in new code (deprecated style).

## 🔗 Related [#-related]

* [Fixtures](/docs/pytest/fixtures)
* [capsys & caplog](/docs/pytest/capsys-caplog)
* [conftest](/docs/pytest/conftest)
* [Mocking](/docs/pytest/mocking)
* [CLI](/docs/pytest/cli)
* [xdist](/docs/pytest/xdist)


---

# xdist (/docs/pytest/xdist)



# xdist [#xdist]

*Pytest · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`pytest-xdist` runs tests in parallel across CPUs or distributed workers. Use `-n auto` locally; ensure tests are isolated (no shared files/DB rows).

## 🔧 Core concepts [#-core-concepts]

| Flag               | Meaning                 |
| ------------------ | ----------------------- |
| `-n N` / `-n auto` | Worker count            |
| `--dist loadscope` | Group by module/class   |
| `--dist loadfile`  | Keep file on one worker |
| `--looponfail`     | Re-run on change (dev)  |
| `worker_id`        | `gw0`, `master`, etc.   |

Each worker is a separate process with its own imports and fixtures (session fixtures run per worker).

## 💡 Examples [#-examples]

**Install & run:**

```shellscript
python -m pip install pytest-xdist
pytest -n auto
pytest -n 4 --dist loadscope
```

**Unique resources per worker:**

```python
import os
import pytest

@pytest.fixture(scope="session")
def db_name(worker_id):
    if worker_id == "master":
        return "test_db"
    return f"test_db_{worker_id}"
```

**Loadfile for order-sensitive modules:**

```shellscript
pytest -n 4 --dist loadfile
```

**Detect xdist:**

```python
def test_only_parallel(worker_id):
    assert worker_id.startswith("gw") or worker_id == "master"
```

## ⚠️ Pitfalls [#️-pitfalls]

* Shared temp paths / ports colliding across workers.
* Session-scoped DB fixtures assuming a single process.
* Flaky tests that only fail under `-n auto`.
* Coverage needing compatible `pytest-cov` settings with xdist.
* Using `--looponfail` in CI (dev-only workflow).

## 🔗 Related [#-related]

* [Install](/docs/pytest/install)
* [Fixtures](/docs/pytest/fixtures)
* [Coverage](/docs/pytest/coverage)
* [CLI](/docs/pytest/cli)
* [tmp\_path](/docs/pytest/tmp-path)
* [Plugins](/docs/pytest/plugins)

