TL;DR
tests/conftest.py
でデプロイ用のlambdaディレクトリをpathに追加する必要がある。
sys.path.append(os.path.join(os.dirname(__file__), '../{lambdaコードのディレクトリ}')
はじめに
pytestでテストを実行した際に、インポートエラーに遭遇した。
importについての理解が浅いせいで、アホみたいな勘違いをして時間を浪費したのでメモる。
sys.path.append
は定義していたのだが、通すpathが間違っていたので苦労した。
ディレクトリ構成
test_app.ppy
で [app.py](http://app.py)
をテストするときに mypkg
をimportしたいケースを考えます。
パッケージ(ディレクトリ?)にはアンダースコア(_)は非推奨らしいので、 lambadasrc
とした。また、ハイフン(-)を利用する場合、パッケージ化したときにimport文で読み込めなくなるので、命名について理解する必要がある。。。
.
├── lambadasrc
│ ├── app.py
│ └── mypkg
│ └── module1.py
└── tests
├── __init__.py
├── conftest.py
└── test_app.py
エラー内容
pytestを実行するとエラー出力される。
.venv ❯ pytest tests -s
~
lambadasrc/app.py:8: in import_mypkg
from mypkg.module1 import func
E ModuleNotFoundError: No module named 'mypkg'
app.pyは下記。
lambadasrc
ディレクトリは、lambdaのデプロイ単位のディレクトリと考えて、その直下からimportする。
from pprint import pprint
import sys
pprint(sys.path)
def import_mypkg():
from mypkg.module1 import func
return func
func = import_mypkg()
def lambada_handler(event, context):
func()
なぜエラーになるかというと、 sys.path
に lambdasrc
へのPATHが通っていないため。(おそらく。。)
['/Users/hoge/import_pytest_202203',
'/Users/hoge/import_pytest_202203/.venv/bin',
'/Users/hoge/.pyenv/versions/3.9.7/lib/python39.zip',
'/Users/hoge/.pyenv/versions/3.9.7/lib/python3.9',
'/Users/hoge/.pyenv/versions/3.9.7/lib/python3.9/lib-dynload',
'/Users/hoge/import_pytest_202203/.venv/lib/python3.9/site-packages']
pytestはtestのソースコードから上の階層にむかって __init__.py
の有無でベースディレクトリを判断する仕様になっているので、 /Users/hoge/import_pytest_202203
が定義されていると理解。
しかし、 lambdasrc
まではpathが通っていないため、import エラーが発生する。
なので、conftest.pyに sys.path.append
をかく。
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '../lambadasrc'))
sys.pathに下記が追加される。testを実行してもエラーは発生しなくなる。
'/Users/hoge/import_pytest_202203/tests/../lambadasrc',
勘違いしてたこと、色々調べてさらに沼ったこと
-
sys.path.append
で、lambadasrc/mypkg
にpathを通していた- 何故かimportするパッケージまでpathが通っている必要があると思いこんでた。。。
-
__init__.py
の有無- pytestでは、ベースディレクトリの判断のために必要
- srcでは、レギュラーパッケージとして認識させる場合は__init__.pyが必要
- 暗黙的な名前空間パッケージ(Implicit Namespace Packages)として利用するなら不要
- まぁ、パフォーマンス的に定義してほうがよいという記事もあるが、、
-
__init__.py
で__all__
定義している情報も見かけ、更に混乱した。。。- __all__の用途は、from~importのワイルドカードを制御するために使用。また、途中のmodule名を省略するために利用しているケースも観測
- from mypkg.hoge from Hoge → from mypkg from Hoge(hogeを省略)
- setup.py(公式では、pyproject.toml,setup.cfg)を使用して、ローカルモジュールとしてpathを通す解決策もあり?
-
pip install -e .
で編集モードでパッケージ作成 - 上記試したが、解決できなかった
- おそらく、本来はパッケージ開発にpytestをインテグレーションするトピックだと推測。(src/とtest/のディレクトリ構成もパッケージ開発のためのトピックか?)
- https://docs.pytest.org/en/latest/explanation/goodpractices.html#src-layout
-
- namespaceもよくわかってない
- デバッグと単純なRunだと、挙動が違いみたい?
他の対応案?
-
pytest
コマンド実行でなく、python -m pytest
実行だとカレントディレクトリをsys.pathに追加するっぽい -
importlib
がちょっと気になる
importlib
: new in pytest-6.0, this mode uses[importlib](https://docs.python.org/3/library/importlib.html#module-importlib)
to import test modules. This gives full control over the import process, and doesn’t require changing[sys.path](https://docs.python.org/3/library/sys.html#sys.path)
.
終わりに
今回のエラーについてググると、ヒットする記事の内容に微妙に差異があるので自分のエラー原因を特定するのに苦労した。ググり力を高めていきたい。
参考
pytest import mechanisms and sys.path/PYTHONPATH - pytest documentation