Pytest Slow Tests: Mark, Skip, Run and Speed Up
Pytest slow tests have two separate fixes. Some tests are slow for a reason, and you should mark them and decide when to run them. The rest are slow by accident, and you can speed them up with parallelism, cheaper fixtures and splitting the suite across CI jobs. This guide shows how to find slow tests, how to use a pytest mark for slow tests, how to skip slow tests by default and run them on demand, and how to use xdist and splitting. It is part of the slow e2e tests cluster.
Find the slow tests
pytest --durations=20 # 20 slowest tests (setup, call, teardown)
pytest --durations=20 --durations-min=1.0 # only those over 1 second
pytest -q --setup-show -k test_name # see fixture setup for one test
--durations reports setup and teardown separately. If a test shows 0.01s call and 3s setup, the fixture is the problem, not the test.
Mark slow tests
Register a marker so pytest does not warn about it, and apply it:
# pytest.ini
[pytest]
markers =
slow: tests that take more than a second or two
e2e: tests that drive a real browser or service
import pytest
@pytest.mark.slow
def test_full_import():
...
You can then select or exclude by marker:
pytest -m "not slow" # fast feedback: skip slow tests
pytest -m slow # run only the slow tests
pytest # everything
Skip slow tests by default, run them on demand
A common pattern is to skip slow tests unless a flag is given. Add this to conftest.py:
import pytest
def pytest_addoption(parser):
parser.addoption("--runslow", action="store_true", default=False, help="run slow tests")
def pytest_collection_modifyitems(config, items):
if config.getoption("--runslow"):
return
skip_slow = pytest.mark.skip(reason="need --runslow to run")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
Then pytest is fast on a laptop and pytest --runslow runs everything. In CI: run -m "not slow" on every PR, and the slow set on merge to main or nightly, in a separate job. That is the single biggest saving for many Python projects. Skipped tests appear as skipped, so they stay visible. Make sure someone owns the slow job; an unrun slow suite is the same as no tests.
Run in parallel with pytest-xdist
pip install pytest-xdist
pytest -n auto # one worker per CPU core
pytest -n 4 # fixed worker count
pytest -n auto --dist loadfile # keep tests from the same file together
-n auto uses all cores, which on a 2-vCPU runner is two workers. Tests must be independent: no shared files, ports or database rows. For databases, give each worker its own, using the worker_id fixture:
@pytest.fixture(scope="session")
def db_name(worker_id):
return "test" if worker_id == "master" else f"test_{worker_id}"
Fixtures with scope="session" run once per worker, not once per run. That is expected, but heavy session fixtures multiply by worker count. --dist loadscope groups by module or class and helps when fixtures are module-scoped.
Cheaper fixtures
- Prefer
scope="module"or"session"for expensive read-only setup. - Wrap database tests in a transaction and roll back instead of recreating tables.
- Use in-memory SQLite only if production semantics allow; otherwise a real database in a container started once per job is more faithful.
- Replace
time.sleepwith a fake clock (freezegunortime-machine) or an event wait. - Use
pytest --lf(last failed) and--ff(failed first) locally.
Split the suite across CI jobs
xdist uses the cores on one runner. To use more runners, split the suite. Plugins such as pytest-split assign tests to groups using stored durations; pytest-shard and similar are other options, so check each project's README for current flags. With pytest-split:
strategy:
fail-fast: false
matrix:
group: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12", cache: pip }
- run: pip install -r requirements.txt pytest-split pytest-xdist
- run: pytest -n auto --splits 4 --group ${{ matrix.group }}
Generate durations once with pytest --store-durations and commit the .test_durations file, so groups are balanced by time, not count. The general method, cost trade-offs and balancing are in the test sharding guide and the planner.
Python-specific CI setup
- Use
actions/setup-pythonwithcache: pip(oruvwith its cache) so dependency installs are restored. - Install only test dependencies for the test job.
- Browser tests from Python: see Playwright tests slow for the Python section.
More CPU
If -n auto is the bottleneck, more cores help directly because xdist scales with them. See faster GitHub Actions runners. compiler.dev was faster than GitHub's 2-core runner on 15 of 19 benchmarked stacks, by 1.1 to 3 times, and comparison mode measures your own jobs. The rest of the pipeline is in the slow CI hub.
FAQ
How do I mark a test as slow in pytest?
Decorate it with @pytest.mark.slow and register slow under markers in pytest.ini to avoid warnings.
How do I skip slow tests in pytest?
Run pytest -m "not slow", or add a --runslow option in conftest.py that skips slow-marked tests unless the flag is present.
How do I run only slow tests?
pytest -m slow.
Is pytest-xdist always faster?
Not for tiny suites, where worker start-up costs more than it saves, and not when tests share state. For suites of a minute or more it usually helps a lot.
Made by compiler.dev. Free tools · Pricing