pytest でテストから自作モジュールを import できない(ModuleNotFoundError)
pytest はプロジェクトのルートではなく、テストファイル側のディレクトリを sys.path に足す。
src レイアウトなら設定の pythonpath か編集可能インストール、フラットな構成なら python -m pytest か tests/__init__.py で解決する。
公開:
要約
アプリのコードは python から普通に動くのに、pytest でテストを実行すると収集の段階で止まる。
ImportError while importing test module '/app/tests/test_calc.py'.
Hint: make sure your test modules/packages have valid Python names.
...
E ModuleNotFoundError: No module named 'myapp'pytest は既定の import モード(prepend)で、テストファイルから上へたどって最初に __init__.py が無いディレクトリを sys.path の先頭に足す。tests/ に __init__.py が無ければ、足されるのは tests/ だけで、プロジェクトのルートや src/ は入らない。
これがエラーの正体だ。
よくある原因
- src レイアウトで
src/が検索パスに無い:src/myapp/に置いたパッケージは、インストールするか検索パスに足さない限り import できない。 - フラットな構成で
pytestを実行している: ルートにmyapp/がある構成でも、pytestコマンドはカレントディレクトリをsys.pathに足さない。 --import-mode=importlibにしている: 公式ドキュメントは新規プロジェクトに importlib モードを勧めているが、このモードでは pytest がsys.pathを変更しない。conftest.pyや__init__.pyの位置で検索パスを足してもらう方法が効かなくなる。- 別の Python の pytest を起動している: グローバルに入った
pytestが先に見つかると、仮想環境に入れたパッケージは見えない。
解決策
1. src レイアウトは pythonpath か編集可能インストール
pytest 7.0 以降は、設定ファイルの pythonpath で検索パスを足せる。
パスは rootdir からの相対で、テストセッションの間だけ有効になる。
# pyproject.toml
[tool.pytest.ini_options]
pythonpath = ["src"]一時的に試すなら PYTHONPATH=src pytest でもよい。
パッケージとして配布する構成なら、pyproject.toml にビルド設定を書いたうえで編集可能インストールにすると、ソースを直してもそのままテストに反映される。
pip install -e .2. フラットな構成は python -m pytest
python -m pytest は Python の標準の動きとしてカレントディレクトリを sys.path に足すので、ルートの myapp/ が見える。
python -m pytest毎回 pytest だけで動かしたい場合は、ルートに空の conftest.py を置くか、tests/__init__.py を置く。
前者はルートが、後者は __init__.py が無い最初の上位ディレクトリ(ここではルート)が sys.path に入る。
3. importlib モードではインストールを前提にする
importlib モードでは、手順 2 のうちルートの conftest.py と tests/__init__.py は効かない。
手順 1 の pip install -e . か pythonpath の設定を使う。python -m pytest は Python 自身がカレントディレクトリを足すので、このモードでも効く。
4. 実行している Python を揃える
python -c "import sys; print(sys.executable)"
python -m pytestpython -m pytest は、いま使っている Python に入った pytest を起動するので、パッケージと pytest の環境が食い違わない。