できない.dev

pytest で「import file mismatch」が出て同じ名前のテストファイルを収集できない

import file mismatch は、テストファイル名を一意にするか、__init__.py を置くか、importlib モードにすれば直る。
同名のテストファイルが同じモジュール名で衝突すると出る。

公開:

要約

テストの収集中に次のエラーが出て、テストが 1 本も実行されずに終わる(終了コード 2)。

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

pytest の既定の import 方式 prepend は、__init__.py の無いディレクトリにあるテストファイルを、そのディレクトリを sys.path の先頭に足してからファイル名だけのモジュール名で import する。tests/integration/test_utils.py と tests/unit/test_utils.py はどちらも test_utils という名前になり、先に import した側が後の側の import の結果として返ってくるので、pytest が「別のファイルだ」と止める。
上の表示は pytest 9.1.1・Python 3.13 で、この 2 ファイルだけを置いたディレクトリで pytest を実行したものだ。

pytest tests/unit のように片方のディレクトリだけを実行すると通るので、全体を回す CI で初めて気付くことが多い。

解決策

1. ファイル名を一意にする

もっとも単純な方法で、ほかの設定に影響しない。

mv tests/integration/test_utils.py tests/integration/test_utils_integration.py

2. __init__.py を置いてパッケージにする

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

モジュール名が tests.unit.test_utils と tests.integration.test_utils のように分かれるので衝突しない。
片方のディレクトリにだけ置いても通ったが、置き忘れで再発しやすいので、テストのディレクトリにはそろえて置く。

3. importlib モードにする

pyproject.toml で import 方式を importlib にすると、pytest は sys.path を変えずに、rootdir からの位置をもとに一意のモジュール名を付けて import する。

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

Good Integration Practices(新しいタブで開く) は新しいプロジェクトにこのモードを勧めている。
ただし、テストファイル同士で import し合うことはできなくなる。
テストで共有する補助関数は conftest.py のフィクスチャにするか、テストの外のパッケージに置く。

よくある原因

  1. 同じ名前のテストファイル: 単体テストと結合テストのように、ディレクトリを分けて同じ名前のファイルを置いている。
  2. __init__.py が無い: テストのディレクトリがパッケージになっていないので、ディレクトリ名がモジュール名に入らない。
  3. 既定の import 方式のまま: ドキュメント(新しいタブで開く)は、テストのディレクトリがパッケージでない場合、テストファイルの名前はほかのテストファイルと重ならないようにする必要があるとしている。

HINT にある __pycache__ の削除は、この構成では効かない。
キャッシュを消してから実行し直しても同じエラーになった。

この手順で直った?