🚦 Pytest Collection vs Marks
Why -m does not prevent Pytest from importing failing tests
When using Pytest, one of the most confusing behaviors for new (and even experienced) users is:
Filtering by mark (
pytest -m mymark) does not prevent Pytest from running collection.
This often leads to a surprising failure:
- You run a test with a specific mark
- But Pytest still tries to import all other test modules
- Some unrelated modules fail during import
- Pytest aborts before your intended test even runs
This article explains why this happens and how to properly control what Pytest collects.
🔄 The Two-Phase Execution Model
Pytest always runs in two phases:
1. Discovery & Collection (import phase)
Pytest:
- Walks the filesystem
- Finds all files matching the test pattern
- Imports every test module
- Executes module-level code and loads fixtures
- Loads
conftest.pyfiles - Builds an internal tree of test functions & classes
If any test file has:
- bad imports
- missing dependencies
- syntax errors
- exceptions in module-level code
… Pytest stops with a collection error, even if you didn’t intend to run that test.
2. Selection & Execution
Only after collection succeeds does Pytest apply:
-mmarkers-kkeyword expressions- Test name selection
- Skips / xfails
- Test ordering plugins
This means:
Marks filter test execution, not test collection.
❗ Why pytest -m mymark Does Not Help
You might expect this to run only tests with a specific mark:
pytest -m datasources_binance
But Pytest’s sequence is:
- Collect everything
- Apply mark filter
- Run filtered tests
If step 1 fails → step 2 never happens.
That’s why a completely unrelated broken test file can block you from running your marked tests.
✔️ What Does Prevent Collection?
Only path-based selection reduces discovery:
Run a single file:
pytest tests/data/sources/binance/test_klines_time_range.py
Run a single test:
pytest tests/data/sources/binance/test_klines_time_range.py::test_compute_time_range_live
Run only a folder:
pytest tests/data/sources/binance
Ignore problematic folders (persistent fix):
pytest.ini:
[pytest]
addopts =
--ignore=tests/data/candle-quality-py
--ignore=tests/data/resampling
--ignore=tests/data/sources/drift
--ignore=tests/integration
Now Pytest won’t even look inside those folders—collection errors disappear.
🔍 Summary Table
| Action | Prevents Collection? | Explanation |
|---|---|---|
pytest -m foo | ❌ No | Filtering happens after collection |
pytest -k foo | ❌ No | Keyword selection also happens post-collection |
pytest path/to/file.py | ✔️ Yes | Pytest collects only this file |
pytest path/to/folder | ✔️ Yes | Pytest collects only in that folder |
--ignore <dir> | ✔️ Yes | Pytest never scans ignored dirs |
Module-level skip (pytest.skip) | ❌ No | Still imported during collection |
🧠 Mental Model Cheat Sheet
- Markers ≠ import filters
-mfilters runnable tests, not loadable modules- Collection failures happen before markers apply
- To isolate test runs, always use file/folder paths or
--ignore
This applies to all marks:
@pytest.mark.slow@pytest.mark.binance@pytest.mark.integration- etc.
None of them block Pytest from importing unrelated test modules.
🎯 Recommended Workflow for Large Repos
When actively developing:
Use path-based targeted runs
pytest tests/data/sources/binance/test_klines_time_range.py
Add opts to pytest.ini to filter out specific dirs
[pytest]
addopts = --ignore=tests/experimental
Only use -m to choose between collected tests
Markers are great for:
- grouping slow tests
- tagging integration tests
- running Binance-related tests selectively
- skipping flaky tests
But they’re not a substitute for controlling collection scope.
🏁 Final Takeaway
If you need to avoid collecting broken tests, use file/folder paths or ignore directives — not marks.
Markers help select what to run, paths help select what to load, and Pytest imports everything before it looks at marks.
If you want, I can turn this into a polished Docusaurus page with front-matter, side navigation category, and rich examples.