# In tests (/docs/python/testing)

The pytest plugin turns what Tracepanel finds into test failures, so an N+1
query fails CI before it reaches production. It comes with the `tracepanel`
package and stays off until you turn it on.

## Turn it on [#turn-it-on]

Pass `--tracepanel` to pytest:

```bash
pytest --tracepanel
```

Or turn it on in the pytest settings, with the kinds of issues that fail a
test:

```toml
# pyproject.toml
[tool.pytest.ini_options]
tracepanel = true
tracepanel_fail_on = ["n+1", "duplicates"]
```

`tracepanel_fail_on` takes the [issue](/docs/issues) names that `has:` takes
in a search, such as `slow-sql` or `error`. The default is `n+1` and
`duplicates`.

The plugin turns Tracepanel on as `tracepanel run` does, with the panel and
the terminal lines off. It starts the project's agent when none is running.

## What a test records [#what-a-test-records]

Each test runs as a `test` trace named after the test. A request the test
makes through Django's test client, Starlette's or FastAPI's `TestClient`,
httpx's ASGI transport or Flask's test client is a trace of its own, linked
to the test's in the console. Fixtures run outside the test's trace, so the
test database's setup doesn't count.

When the test passes, the agent looks at its traces. An issue of a kind in
`tracepanel_fail_on` fails the test with the issue, its query, the line of
code that ran it and a link to the trace:

```text
tracepanel found 1 issue in this test:

N+1: 5 similar queries · SELECT auth_group
  SELECT "auth_group"."name" FROM "auth_group" ... WHERE "user_id" = %s
  at blog/views.py:9 in users
  in GET /users/ → http://127.0.0.1:52811/#/t/01M5VH6BFMFPA7X692DKQV2ANZ
  Load the related rows in one extra query: add `prefetch_related()` ...
```

## Change the rules for a test [#change-the-rules-for-a-test]

The `tracepanel` mark changes the rules for a test, a class or a module. It
takes keyword arguments:

| Argument | What it does |
| --- | --- |
| `max_queries` | Fails the test when it and its requests make more queries than this. |
| `fail_on` | Replaces the kinds of issues that fail the test. |
| `allow` | Takes kinds of issues out of those that fail the test. |

The `tracepanel_trace` fixture gives the test its trace: `.queries` counts the
queries so far, and `.issues` lists what the agent found in the test's
requests so far. `tracepanel.max_queries(n)` fails a block that makes more
than `n` queries, its requests' included:

```python
import pytest

import tracepanel


@pytest.mark.tracepanel(max_queries=10, allow=["n+1"])
def test_feed(client, tracepanel_trace):
    client.get("/feed/")
    assert tracepanel_trace.queries <= 10
    with tracepanel.max_queries(2):
        client.get("/feed/?page=2")
```

## Without an agent [#without-an-agent]

With `agent = "off"`, or on a platform with no agent, the tests run as usual
and one warning says that issues aren't checked. Query counts need no agent,
so `max_queries` still works.

The plugin works with pytest-django and pytest-xdist, where every worker sends
its tests to the same agent, and with async tests under anyio or
pytest-asyncio.
