Skip to Content
Diagnose pytest Discovery and src-Layout Import Errors
Intermediate
0 likes
Tutorials

Diagnose pytest Discovery and src-Layout Import Errors

Separate zero collected tests from an import failure, install into the correct environment, and prove the selected test catches a broken assertion.

“No tests collected” and an import error are different failures. Changing an assertion will not fix discovery, and renaming a file will not install your package. This lab separates those stages in a small src-layout project, then proves the selected test can fail.

1. Use one interpreter from the project root

These PowerShell commands were checked with Python 3.12.14 and pytest 9.1.1. Start in a new empty practice directory. The broader introduction to assertions and test design is in the course lesson.

python -m venv .venv
$py = (Resolve-Path .venv/Scripts/python.exe).Path
& $py -m pip install pytest==9.1.1
& $py -m pytest --version
& $py -c "import sys; print(sys.executable)"

On macOS/Linux the environment interpreter is .venv/bin/python; use that same interpreter for pip and pytest. Activation is optional when you call the interpreter directly. Run every following command from this project's root.

2. Build a src-layout fixture

Create pyproject.toml:

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "xt-discovery-lab"
version = "0.0.1"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = ["--import-mode=importlib"]

Create an empty src/discovery_lab/__init__.py, then save src/discovery_lab/totals.py:

def total_cents(prices):
    return sum(prices)

Create tests/checks_totals.py with this deliberately undiscoverable function:

def check_total():
    assert 125 + 275 == 400
& $py -m pytest --collect-only -q
$LASTEXITCODE

Expect no tests collected and exit code 5. With the configuration above, normal discovery looks under tests for test_*.py or *_test.py files, and for test_ functions. Naming both the file and function matters.

3. Fix naming before investigating imports

Rename-Item tests/checks_totals.py test_totals.py
& $py -m pytest --collect-only -q

The file name now matches, but the function still does not, so zero tests remain the expected result. Now replace the file with:

from discovery_lab.totals import total_cents

def test_total():
    assert total_cents([125, 275]) == 400

Run collection again. In this fresh environment, it stops with ModuleNotFoundError: No module named 'discovery_lab' and exit code 2. pytest found and imported the test module, but the src-layout application package is not installed. This particular run is a collection failure; exit code 2 can also represent other interruptions.

4. Install the application into that environment

& $py -m pip install -e .
& $py -c "import discovery_lab; print(discovery_lab.__file__)"
& $py -m pytest --collect-only -q

The package path should point inside this practice project's src/discovery_lab. Collection should print:

tests/test_totals.py::test_total

1 test collected

The duration and path separators vary. An editable install connects this environment to the source package; it does not install the project into every Python interpreter. --import-mode=importlib avoids pytest adding test directories to the import path. Do not add random sys.path edits to hide a missing installation.

5. Run the collected node and prove failure detection

& $py -m pytest -q tests/test_totals.py::test_total
$LASTEXITCODE

Expect one passed test and exit code 0. Temporarily change the expected result to 401. Repeat the same command: it must report assert 400 == 401, one failed test and exit code 1. Restore 400 and run & $py -m pytest -q to confirm the whole practice suite passes.

Observed resultNext check
No tests collected; code 5Check root directory, testpaths, file names and function names.
Collection import error; code 2 in this labCheck the traceback, environment interpreter and editable package installation.
One collected test fails; code 1Read the assertion values and application behavior.
One test passes; code 0Confirm its node ID and deliberately break its assertion once.

6. Diagnose an existing project without changing its rules

First run collection with the project's normal configuration and inspect the printed root directory. Custom python_files, python_functions, ignore rules and plugins can change discovery. An explicit file or node ID helps isolate a target, but does not prove that a normal full-suite run discovers it. If another interpreter imports a different copy, print its executable and the application's __file__ there too.

Exercise: rename the passing function to check_total again. Normal collection returns to zero tests. Restore test_total and the passing run. Keep this distinction in a bug report: discovery, collection/import, execution, or assertion.

References: pytest discovery and src layouts and pytest exit codes.

About

Separate zero collected tests from an import failure, install into the correct environment, and prove the selected test catches a broken assertion.

Responsible
Jay P
Last Update
09/21/2026
Reading Time
4 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.