Posted at

Python自作パッケージのunittestをそれっぽいディレクトリ構成で実施する際にModuleNotFoundErrorを回避する


はじめに


  • Pythonパッケージ(Pypiで公開)を作成しようとしていて

  • かつそれっぽいPythonディレクトリ構成でリリースを検討した際にハマった点を備忘として記録

  • あとはIDE(Pycharm)様々という感謝の気持ち


こんなエラー・事象にハマった


パッケージ構成



  • 再現可能なテストを実行するために、かつ公開パッケージとして適切なディレクトリ構造にするために作成するパッケージのディレクトリとテストディレクトリは分類し、以下のようなパッケージ構成にしています。


    • 偉い人がそう言っている

    • 世の素晴らしい公開パッケージの構成もそうなっているため



  • パッケージ構成


Macintosh:$ tree

Project
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs
│   ├── Makefile
│   ├── conf.py
│   ├── index.rst
│   └── make.bat
├── setup.py
├── my_package_name
│   ├── __init__.py
│   ├── my_package_core.py
│   └── my_package_helper.py
└── tests
├── __init__.py
├── test_my_package_helper.py
└── test_my_package_core.py


いざテスト


  • testsディレクトリ配下のtest_my_package_core.pyのテストを実装(my_package_nameのmy_package_core.pyのあるクラスのみをimport)し、ターミナルからテスト実行しようとしたところ、エラーが発生した。


test_my_package_core.py

import os

import traceback
import unittest

from my_package_name.my_package_core import PackageClass

class TestPage(unittest.TestCase):

()

if __name__ == '__main__':
unittest.main()



  • テスト実行すると ModuleNotFoundError

$ python tests/test_my_package_core.py

Traceback (most recent call last):
File "my_package_core.py", line 5, in <module>
from my_package_name.my_package_core import PackageClass
ModuleNotFoundError: No module named 'my_package_name'


  • IDEで作成したProjectのパスはsys.pathに登録されるのでは?と確認してみる

(project) Macintosh:$ python

Python 3.5.2 (default, Dec 30 2016, 02:25:25)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.path)
['', '/Users/name/.pyenv/versions/3.5.2/lib/python35.zip', '/Users/name/.pyenv/versions/3.5.2/lib/python3.5', '/Users/name/.pyenv/versions/3.5.2/lib/python3.5/plat-darwin', '/Users/name/.pyenv/versions/3.5.2/lib/python3.5/lib-dynload', '/Users/name/project/lib/python3.5/site-packages', '/Users/name/project/lib/python3.5/site-packages/setuptools-39.1.0-py3.5.egg', '/Users/name/project/lib/python3.5/site-packages/pip-10.0.1-py3.5.egg']


  • 登録されていないことが分かった。

  • それは失敗するよね、と思いつつあれ確かPycharmのテスト実行機能では問題なかった気が・・・と試してみると


    • image.png

    • 成功してしまった




Why?


Pycharmコンソールで実行する場合


  • Pycharm下部のこのボタン


    • image.png



/Users/name/project/bin/python "/Applications/PyCharm CE.app/Contents/helpers/pydev/pydevconsole.py" 51470 51471

import sys; print('Python %s on %s' % (sys.version, sys.platform))
sys.path.extend(['/Users/name/PycharmProjects/project'])
PyDev console: starting.
Python 3.5.2 (default, Dec 30 2016, 02:25:25)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin


  • Pythonコンソールの初期化処理でsys.path.extend(['/Users/name/PycharmProjects/project'])を実行してくれていることが分かった。


Pycharmのテスト機能で実行する場合

$ Launching unittests with arguments python -m unittest test_package_core.TestPage in /Users/name/PycharmProjects/project/tests


  • なるほど -m など様々な点で配慮していただいているようで。


IDEサマサマ


  • Pycharmが意識せず吸収してくれている点が多々ある

  • Pythonコンソール起動時の作成したプロジェクトフォルダをsys.pathに追加する処理や

  • Pycharmのユニットテスト実行機能が python -m を付与し、ModuleNotFoundを回避してくれている


とはいえ、分かったからにはterminalからだろうが同じ状態に出来ることを目指したい


testcase側でProjectパスを追加することで対応


test_my_package_core.py

import os

import sys
import traceback
import unittest

# 以下1lineを追加
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from my_package_name.my_package_core import PackageClass

class TestPage(unittest.TestCase):

()

if __name__ == '__main__':
unittest.main()


$ python tests/test_my_package_core.py


Success

以上