Skip to content
← Back to explainers

Python explainer

Lay Out a Project So pytest Imports the Right Files

About 4 min to read. Allow extra time to work through examples.

Fix the import errors pytest gives when two test files share a name or your code lives under src/, and keep shared fixtures in conftest.py.

This article uses Python. You can make it your practice mode to browse related content across the site.

pytest imports each test file as a module, and by default it names the module after the file. So tests/unit/test_utils.py and tests/integration/test_utils.py both become test_utils, and the second import fails:

ERROR collecting tests/unit/test_utils.py
import file mismatch:
imported module 'test_utils' has this __file__ attribute:
  .../tests/integration/test_utils.py
which is not the same as the test file we want to collect:
  .../tests/unit/test_utils.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules

Deleting __pycache__, as the hint suggests, does not help here. The problem is the module name, not a stale file.

Give each test module its own name

The simplest fix is to rename one file, for example to test_utils_integration.py.

Or add an empty __init__.py to tests/ and to each folder under it:

tests/__init__.py
tests/unit/__init__.py
tests/unit/test_utils.py
tests/integration/__init__.py
tests/integration/test_utils.py

A folder with an __init__.py is a package, so pytest names each module after its path through the packages. The two modules become tests.unit.test_utils and tests.integration.test_utils.

Or switch pytest to its importlib import mode. In that mode pytest names each module after the file's path and leaves the import path alone. Two files can then share a name with no __init__.py files. Set it in pyproject.toml:

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

pytest has no setting for the import mode, so pass the command-line flag through addopts. testpaths tells pytest where to look for tests when you run it without naming any. pytest 9 also reads a [tool.pytest] table, which the next article covers. The ini_options table works on older versions too.

The importlib mode has a cost. The test's folder is no longer on the import path. So a test cannot import a helper module that sits next to it, as in from helpers import make_user. Share setup through fixtures instead, or move the helpers into a package your tests can import.

Share fixtures through conftest.py

A conftest.py file holds fixtures for the tests in its folder and in every folder below it. pytest finds it on its own, and a test asks for one of its fixtures by naming it as a parameter. A conftest.py in a subfolder can add fixtures, or replace one that has the same name.

pytest loads conftest.py itself, so do not import it from a test. Put helper functions that tests call directly in an ordinary module.

Make your code importable from the tests

Say your project keeps its code under src/:

pyproject.toml
src/shop/__init__.py
tests/test_total.py

Run pytest here, and the test's from shop import total fails with ModuleNotFoundError: No module named 'shop'. pytest put tests/ on the import path, not src/. Either of two fixes works.

The first tells pytest where your code is. pytest adds each pythonpath folder, relative to the project root, to the import path for the run:

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]

The second is an editable install. Run python -m pip install -e . in your virtual environment. pip installs your project, but Python reads its files from your src/ folder, so an edit shows up without reinstalling.

Check your understanding Your tests live in tests/api/test_models.py and tests/db/test_models.py, and pytest reports "import file mismatch". Why does deleting __pycache__ not fix it? Show answer

Both files import as a module named test_models, and Python can hold only one module under a name. No stale file causes it, so clearing caches changes nothing. Rename one file, add an __init__.py to tests/ and to each folder under it, or switch to the importlib import mode.

Going further

pythonpath and an editable install both let tests read files straight from src/. So a test can pass even when a file is missing from the package you publish. A wheel is the built file that pip installs, and the one you upload to PyPI. To test exactly what users get, build the wheel, install it into a new virtual environment, and run the tests from outside the source folder. print(shop.__file__) shows which copy a test imported. The packaging track covers building wheels.

The rootdir line in pytest's header names the folder pytest treats as the project root. Test IDs are paths relative to it, and the .pytest_cache folder lives in it. pytest does not add it to the import path.

python -m pytest adds the current folder to the import path, and plain pytest does not. In a flat project, with shop/ next to tests/, one command can pass where the other fails. Pick one and use it in CI too.

step 17 of 19

Testing with pytest and Hypothesis

Up next Turn On pytest's Strict Checks