In tests
The pytest plugin runs each test as a trace and fails a test with an N+1, duplicate queries or too many queries.
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
Pass --tracepanel to pytest:
pytest --tracepanelOr turn it on in the pytest settings, with the kinds of issues that fail a test:
# pyproject.toml
[tool.pytest.ini_options]
tracepanel = true
tracepanel_fail_on = ["n+1", "duplicates"]tracepanel_fail_on takes the issue 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
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:
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
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:
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
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.