Speed up your test suite

Fix slow pytest startup and heavy imports in tests

pytest takes seconds before the first test runs? Find slow imports with python -X importtime, set testpaths, trim conftest.py imports and disable plugins you don't use.

Before pytest runs a single test it starts Python, loads every installed plugin, imports your conftest.py files, and collects tests by walking directories and importing every test module, along with everything those modules import. On a large project that can take 10 seconds or more, and you pay the startup part even when you run one test with pytest tests/test_orders.py::test_checkout.

This is the time you feel most: it's the wait between saving a file and seeing a result. A suite that runs in 3 seconds after 12 seconds of startup feels slow.

Where the time goes before the first test

  • Plugins. pytest imports every plugin registered through an entry point, whether you use it or not. Some import heavy dependencies at load time.
  • conftest.py. The root conftest.py is imported on every run. If it imports your whole application, a data science stack or an SDK client, every run pays for it. pytest-django calls django.setup(), which imports every app in INSTALLED_APPS and their models.
  • Collection. Without testpaths, pytest walks everything under the root directory: docs/, frontend/, generated files, data directories. Every test module it finds is imported, and so is everything it imports at module level.

Measure it

Python can print the import time of every module. Run it over collection only:

$ python -X importtime -m pytest --collect-only -q 2> importtime.log
$ sort -t'|' -k2 -n -r importtime.log | head -20
import time:       282 |     254254 | pytest
import time:      1149 |      88471 |     _pytest.assertion.rewrite
...

The second column is the cumulative time in microseconds, including everything that module imported. Look for the big third-party packages and for your own modules that pull in half the codebase. pytest --collect-only -q on its own tells you how long collection takes in total.

The fix

Tell pytest where the tests are

testpaths limits where pytest looks when you run it without arguments:

# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
norecursedirs = ["*.egg", ".*", "_darcs", "build", "CVS", "dist", "node_modules", "venv", "{arch}", "docs", "frontend"]

Setting norecursedirs replaces pytest's default list instead of adding to it, so keep the defaults (shown above) and append your own directories. testpaths is usually the bigger win: pytest never even lists the directories outside it.

Keep conftest.py light

The root conftest.py is imported on every run, so a heavy import there costs every run. Move fixtures that need a heavy library into a conftest.py in the directory whose tests use them:

 # tests/conftest.py
-import pandas as pd
-from myapp.reports.engine import ReportEngine
 import pytest
# tests/reports/conftest.py: only imported when tests in tests/reports/ are collected
import pandas as pd
import pytest

from myapp.reports.engine import ReportEngine


@pytest.fixture
def report_engine():
    return ReportEngine(pd.DataFrame())

Imports only used in type hints can go under if TYPE_CHECKING:. In application code, importing a heavy library inside the one function that needs it also works; Speeding up Django startup times with lazy imports walks through finding those imports with -X importtime, moving them, and using ruff's banned-module-level-imports rule to keep them from coming back. The same changes speed up manage.py commands and your app's startup, not just tests.

On Python 3.15 you can keep the import at the top and make it lazy with the new lazy keyword (PEP 810): the module is only loaded when the name is first used.

lazy import pandas as pd

Disable plugins you don't use

List the plugins installed in the environment and their entry point names:

$ python -c "from importlib.metadata import entry_points; print([e.name for e in entry_points(group='pytest11')])"
['xdist', 'xdist.looponfail', 'randomly', 'pytest_cov']

Block the ones this project doesn't need with -p no:<name>, on the command line or in addopts. A blocked plugin isn't imported at all:

[tool.pytest.ini_options]
addopts = ["-p", "no:randomly"]

If you'd rather opt in than opt out, --disable-plugin-autoload (pytest 8.4+, or the PYTEST_DISABLE_PLUGIN_AUTOLOAD environment variable) loads only the plugins you name with -p.

Imports while tests run

The other half of the problem is imports that happen inside a test. When application code imports a module lazily (inside a function), the first test that calls that code pays for the import. That test looks slow, and which test it is changes with test order, so --durations and per-test timings point at the wrong place. Under pytest-xdist every worker pays it again.

Moving the import to the top of the test module or a conftest.py doesn't make the suite faster by itself: it moves the cost to collection, where it shows up once and stops distorting test timings. The real saving comes from not importing the module at all: stub the heavy dependency in tests that don't need it, or split the module so the cheap part doesn't drag in the expensive one.

Keep the tests correct

  • Don't block a plugin your tests rely on (for example pytest-django or pytest-asyncio): its fixtures and markers disappear, and with --strict-markers the run fails.
  • Narrowing testpaths can silently stop collecting tests that live elsewhere. Compare the counts from pytest --collect-only -q before and after.
  • A stubbed dependency needs at least one test that uses the real thing.

How flakytest finds it

flakytest starts profiling before your conftest.py files are imported, and its session timeline splits the run into startup, collection, tests and teardown, with the slowest imports during startup and collection. When startup and collection take at least 3 seconds and at least 10% of the run, it suggests pytest takes a long time before the first test runs and lists the slowest modules.

Inside tests, time spent in importlib goes to the imports category. When it's at least a second and 2% of test time, flakytest suggests Modules are imported while tests run, with the tests and the lines of your code that triggered the imports.

Further reading

Related guides

  • How to speed up a slow pytest suite: A checklist for slow Python test suites, from the fixes that usually save the most time (password hashing, migrations, test data) to parallelism and CI. Each item links to a detailed guide.
  • Expensive function-scoped pytest fixtures: A pytest fixture without a scope runs again for every test that uses it. Give expensive fixtures module or session scope, or split them into a shared part and a cheap reset.
  • Mock and block real network calls in tests: Real HTTP calls make pytest suites slow and flaky. Mock at the transport with responses, respx, pytest-httpx, aioresponses, moto or vcrpy, then block the network with pytest-socket.
  • How to profile a pytest suite: Profile slow pytest tests with cProfile, pytest-profiling, py-spy, pyinstrument, Python 3.15's tachyon or flakytest: the commands, what each one shows, and which to use when.

Updated October 18, 2026 · Suggest a change