Shipping tests with your package (in-tree tests)#
Throughout this guide we recommend keeping your tests/ directory outside
of your package directory (an “out-of-tree” layout). This keeps your wheel
small and, combined with the src layout, guarantees that
your tests run against the installed package rather than the files in your
working directory.
Some packages, however, benefit from shipping their tests inside the package (“in-tree” tests). This is common in the scientific Python ecosystem: NumPy, SciPy, and scikit-image all do it.
When in-tree tests make sense#
Consider in-tree tests if your package:
Contains compiled code (C, C++, Cython, Rust, or Fortran). A wheel built on one platform may import fine but produce wrong results on another. Letting users run the test suite against the wheel they installed is the most reliable way to find out.
Is built by downstream packagers such as conda-forge, Debian, or Spack. Packagers routinely run a package’s test suite as part of their build, and they can only do so if the tests ship with the package.
Has users who need to verify an installation on an HPC cluster or another locked-down environment where they can’t clone the repository.
If your package is pure Python with a small test suite, out-of-tree tests are simpler, and we recommend sticking with them.
Layout#
Move tests/ inside your package directory and give it an __init__.py so
that it is discovered and installed like any other subpackage:
src/
mypackage/
__init__.py
core.py
tests/
__init__.py
test_core.py
docs/
pyproject.toml
Make sure the tests ship
Check your build backend’s package-discovery settings. If you list packages
explicitly, add mypackage.tests. After building, inspect the wheel
(unzip -l dist/*.whl) to confirm that the test files are included.
Run tests against the installed package#
With tests inside the package, run them using pytest’s --pyargs flag. This
tells pytest to import mypackage from the active environment, locate its
tests subpackage, and run it, regardless of your current working directory:
pytest --pyargs mypackage
Many scientific packages also expose a convenience function so that users don’t need to remember the flag:
# src/mypackage/__init__.py
def test(*args):
"""Run the mypackage test suite."""
import pytest
return pytest.main(["--pyargs", "mypackage", *args])
>>> import mypackage
>>> mypackage.test()
Because pytest is only imported inside the function, it stays an optional
dependency. Add it to a test extra in your pyproject.toml:
[project.optional-dependencies]
test = ["pytest"]
Pitfalls#
Running tests on the source tree by accident. This is the main reason we
recommend out-of-tree tests for beginners. If you run
pytest src/mypackage/tests from the repository root, Python may import your
un-built source files instead of the installed package. With compiled code,
this surfaces as confusing ImportErrors about missing extension modules.
Always use pytest --pyargs mypackage (or a nox or tox session that
installs the package first) rather than pointing pytest at a path.
Test data. Don’t ship large datasets inside your wheel. See don’t include test suite datasets in your package for alternatives such as Pooch.
Wheel size. Every test file is downloaded by every user. Keep the in-tree suite focused. Very slow or data-heavy tests can stay out of tree and run only in CI.
For a real-world example, see how
NumPy organizes
numpy/tests and numpy/<submodule>/tests, and exposes numpy.test().