Tag the Slow Tests, Get Your Inner Loop Back
If your test suite takes a minute, your inner loop is dead. You stop running tests on every save, your AI coding agent burns its iterations waiting, and every small change costs a coffee break. The fix is not deleting tests — it's tagging the slow ones so the default run is fast.
The problem: a few tests hold the whole suite hostage
Most suites are lopsided: hundreds of unit tests that finish in milliseconds, plus a handful of integration tests that hit a database, a container, or a network and take seconds each. Run everything together and the slowest 5% sets the pace for 100% of your runs. Find your offenders with pytest's built-in profiler:
pytest --durations=10 --durations-min=1.0
That prints the 10 slowest tests over 1 second. Those are your tagging candidates.
The mechanism: markers + -m
Register the markers once so a typo can't silently un-tag a test:
# pytest.ini
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: touches a real database, network, or container
addopts = --strict-markers
Decorate the heavy tests:
import pytest
@pytest.mark.slow
@pytest.mark.integration
def test_full_checkout_flow(postgres, stripe_sandbox):
...
Then your daily run is:
pytest -m "not slow"
Marker expressions take not, and, and or, so -m "not slow and not integration" or -m "integration or slow" both work. --strict-markers makes pytest error on any unregistered marker instead of only warning — worth turning on from day one.
Rules of thumb
| Test kind | Typical time | Marker | Where it runs |
|---|---|---|---|
| Pure unit test | < 50 ms | none | every save, local + CI |
| Component test with fakes | < 500 ms | none | every save, local + CI |
| Hits DB / filesystem / container | 1–10 s | integration | pre-push + CI |
| End-to-end, external APIs | 10 s + | slow | CI only |
The target: the unmarked set should complete in about a second, so you (and your coding agent) run it reflexively after every edit instead of batching changes and hoping.
Why this matters more with AI agents
A coding agent iterates in a loop: edit, run tests, read failures, edit again. If each loop costs 60 seconds of test time, a 10-iteration fix takes 10 minutes of pure waiting — and agents run tests far more often than humans do. Give the agent the fast command (pytest -m "not slow") in your project instructions, and reserve the full suite for CI, where a few minutes of wall-clock time is free.
Power-user moves
- Fail-first ordering.
pytest --lfre-runs only the tests that failed last time;pytest --ffruns those first, then the rest. Combined with-m "not slow", a red-to-green loop often drops below the time it takes to switch windows. - CI runs the complement. Your CI job should run the full suite — or explicitly
pytest -m "slow or integration"in a separate parallel job — so nothing tagged ever goes unexecuted. A marker that's never run anywhere is a deleted test with extra steps. - Make markers mean something.
integrationshould be a contract ("needs Docker"), not a vibe. Document each marker in the registration line — everything after the colon inpytest.iniis its description, shown bypytest --markers. - Budget the fast set. Re-run
--durations=10monthly. Tests creep; a "unit" test that grew a 2-second fixture gets tagged or fixed.
Resources
- How to mark test functions with attributes — pytest docs
- Working with custom markers — pytest examples
- Profiling test durations (
--durations) — pytest usage docs - Re-running failures with
--lf/--ff— pytest cache docs
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.