LoginSignup
2
5

More than 3 years have passed since last update.

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

Posted at

はじめに

  • 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

以上

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5