はじめに
VS CodeのTest Explorerでpytestを使っていると、テスト自体は存在するのに、次のようなエラーで検出されないことがあります。
ModuleNotFoundError: No module named 'app'
この問題を調べていると、次の2つのコマンドで結果が変わることがあります。
pytest
python -m pytest
どちらもpytestを実行していますが、Pythonのモジュール探索パスであるsys.pathが同じとは限りません。
今回は、最小構成で違いを確認し、VS Code側でどこを見るべきか整理します。
最小構成を作る
ディレクトリ構成は次のとおりです。
pytest-path-example/
├── app.py
└── tests/
└── test_app.py
app.pyには、単純な足し算関数を作ります。
def add(a: int, b: int) -> int:
return a + b
テストは次のようにします。
from app import add
def test_add():
assert add(2, 3) == 5
pytestでは失敗する
プロジェクト直下でpytestを実行します。
pytest -q
手元の環境では、次のようになりました。
E ModuleNotFoundError: No module named 'app'
1 error
一方、次のコマンドでは成功しました。
python -m pytest -q
. [100%]
1 passed
同じpytestなのに結果が異なりました。
なぜ結果が変わるのか
違いは、pytestをどのように起動しているかです。
pytest
こちらは、環境にインストールされたpytestという実行ファイルを直接呼び出します。
一方で、
python -m pytest
は、現在選択されているpythonからpytestモジュールを起動します。この実行方法では、現在のディレクトリがPythonのモジュール探索対象に追加されます。
そのため、プロジェクト直下にあるapp.pyをimportできました。
つまり、問題はテストコードではなく、次のどちらかかもしれません。
使っているPythonが違う
モジュールを探す場所が違う
実行環境を確認する
まず、Pythonの実体を確認します。
python -c "import sys; print(sys.executable)"
次にpytestの場所を確認します。
macOSまたはLinuxでは、次のコマンドを使えます。
which python
which pytest
Pythonから見えているモジュール探索パスも確認できます。
python -c "import sys; print(*sys.path, sep='\n')"
仮想環境を切り替えたあとにVS Codeを開いた場合などは、pythonとpytestが別の環境を指していることがあります。
VS Code側で確認する場所
VS Codeでは、まずコマンドパレットから次を実行します。
Python: Select Interpreter
ここで、ターミナルから確認したPythonと同じ環境を選びます。
pytestの基本設定は次のようになります。
{
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestArgs": [
"tests"
]
}
テストが検出されない場合は、次の画面を開きます。
表示
→ 出力
→ Python
Test Explorerに表示される短いエラーだけでなく、テスト検出時の詳しいログを確認できます。
cwdとrootdirはPYTHONPATHではない
VS Codeには、テストの作業ディレクトリを指定する設定もあります。
{
"python.testing.cwd": "${workspaceFolder}"
}
ただし、ここで注意が必要です。
pytestのrootdirは、設定ファイルの探索やテストIDの基準として使われる値です。rootdirを変更しただけで、対象ディレクトリが自動的にsys.pathへ追加されるわけではありません。
つまり、次の3つは別のものです。
cwd コマンドを実行する現在位置
rootdir pytestが認識するプロジェクトの基準位置
sys.path Pythonがimport先を探す場所
cwdやrootdirだけを何度も変更しても直らない場合は、sys.pathを確認した方が早いです。
小規模プロジェクトでの修正方法
単純な構成なら、pyproject.tomlにPythonパスを明示できます。
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
これで、次のコマンドでもプロジェクト直下のapp.pyをimportできます。
pytest -q
1 passed
ただし、本格的なPythonパッケージでは、srcレイアウトを使ってパッケージとしてインストールする方が安全です。
project/
├── pyproject.toml
├── src/
│ └── myapp/
│ ├── __init__.py
│ └── core.py
└── tests/
└── test_core.py
開発中はeditable installを使います。
python -m pip install -e .
python -m pytest
この方法なら、VS Code、ターミナル、CIで同じimport規則を使いやすくなります。
testファイルでsys.pathを書き換えない
一時的な回避策として、テストファイルに次のようなコードを書きたくなります。
import sys
sys.path.append("..")
しかし、これはテストを実行した場所によって挙動が変わります。
テストコード内でパスを修正するのではなく、次のいずれかで管理する方が再現性があります。
pyproject.tomlでpythonpathを指定する
パッケージをeditable installする
python -m pytestで実行する
まとめ
VS Codeでpytestが検出されないとき、確認すべきなのはpytestEnabledだけではありません。
python -c "import sys; print(sys.executable)"
python -c "import sys; print(*sys.path, sep='\n')"
pytest -q
python -m pytest -q
この4つを比較すると、Python環境の違いなのか、importパスの違いなのかを切り分けられます。
VS CodeのTest Explorerは便利ですが、実際にテストを動かしているのはPythonとpytestです。
「どのPythonが、どのディレクトリから、どこを探しているか」を確認することが、テスト検出エラーを解決する近道でした。