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

# はじめに
* Pythonパッケージ(Pypiで公開)を作成しようとしていて
* かつそれっぽいPythonディレクトリ構成でリリースを検討した際にハマった点を備忘として記録
* あとはIDE(Pycharm)様々という感謝の気持ち

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

## パッケージ構成

* 再現可能なテストを実行するために、かつ公開パッケージとして適切なディレクトリ構造にするために作成するパッケージのディレクトリとテストディレクトリは分類し、以下のようなパッケージ構成にしています。
    * 偉い人が[そう](https://www.kennethreitz.org/essays/repository-structure-and-python)言っている
    * 世の素晴らしい公開パッケージの構成もそうなっているため

* パッケージ構成

```bash
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```

```terminal
$ 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に登録されるのでは？と確認してみる

```bash
(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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473617/25c363aa-c00f-cef4-000a-5384655f2ac5.png)
    * 成功してしまった

## Why？
### Pycharmコンソールで実行する場合
* Pycharm下部のこのボタン
    * ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473617/2b491cfa-a981-9461-be3d-931709673e87.png)

```Pythonコンソール
/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のテスト機能で実行する場合

```bash
$ 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()
```

```terminal
$ python tests/test_my_package_core.py

Success
```


以上