業務でPythonによるテスト駆動開発(TDD)を行うことになり、pytestを用いました。ただ、ディレクトリ構造が正しく認識されないとModuleNotFoundErrorに阻まれて実装が進まなくなってしまうため、備忘録を兼ねてまとめることにしました。
環境構築
pytestのインストールが必要です。下記コマンドの実行でインストールが可能です。
pip install pytest
pytestを毎回手動実行するのは煩雑であるため自動実行できる様にしておこうと思い、pytest-watchもインストールしました。起動しておくとソースが更新される毎に自動でpytestによるテストが実行されます。
pip intall pytest-watch
起動方法
ptw
最も簡単な方法
最も簡単な方法はソースファイルとテストソースを同じディレクトリ内に格納する方法です。これ以降、以下のサンプルソースを使って説明します。空ファイルに近い内容ですが、pytestが実行されているかの確認には使えます。
ソースファイル(hogehoge.py)
class HogeHoge:
pass
テストソース(test_hogehoge.py)
from hogehoge import HogeHoge
def test_init():
HogeHoge()
pytestはtest_hogehoge.pyまたはhogehoge_test.pyというファイル名を探して実行される仕様となっているので必ずテストソースのファイル名にはtestの文言を含めましょう。別のターミナルをもう1つ起動しソースが格納されているディレクトリに移動した上でpytest-watchを起動してみましょう。pytestが実行されます。
正常にテストが完了するとこの様な表示が出ます
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-5.1.1, py-1.8.0, pluggy-0.12.0
rootdir: sample-tdd
collected 1 item
test_hogehoge.py . [100%]
============================== 1 passed in 0.01s ===============================
ソースディレクトリを切り分ける方法
ソースファイルが少ない時は上記の方法でも良いでしょう。しかし、ソースファイルが増えればソースファイルとテストソースが混在してしまい、目的のソースファイルが探しにくくなります。故にソース用ディレクトリとテスト用ディレクトリに切り分けます。
ディレクトリの切り分け
今回は以下の様なディレクトリ構造にしました。pytestはカレントディレクトリかtestsというディレクトリにテストを探しに行く仕様となっている様なので、testsというディレクトリ名にしています。
sample_tdd/
├── modules
│ └── hogehoge.py
└── tests
└── test_hogehoge.py
__init__.py
の準備
modulesディレクトリに__init__.pyを格納します。記載内容は以下の通りです。
from .hogehoge import HogeHoge
__all__ = [
'HogeHoge',
]
テストソースの修正
test_hogehoge.pyの内容を以下の様に修正します。
- from hogehoge import HogeHoge
+ from modules.hogehoge import HogeHoge
def test_init():
HogeHoge()
結果として以下の様なディレクトリ構造になりました。
sample_tdd/
├── modules
│ ├── __init__.py
│ └── hogehoge.py
└── tests
└── test_hogehoge.py
pytest-watchを実行すると以下の様な結果となりました。
______________ ERROR collecting sample_tdd/tests/test_hogehoge.py ______________
ImportError while importing test module 'sample_tdd/tests/test_hogehoge.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
sample_tdd/tests/test_hogehoge.py:1: in <module>
from modules.hogehoge import HogeHoge
E ModuleNotFoundError: No module named 'modules'
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 0.05s ==============================
conftest.pyを追加
プロジェクトのrootディレクトリ(この例ではsample_tddディレクトリ)に空ファイルconftest.pyを追加します。追加には以下のコマンドを実行します。これはpytestの仕様上必要なファイルです。
touch conftest.py
ディレクトリ構造は以下の様になりました。
sample_tdd/
├── conftest.py
├── modules
│ ├── __init__.py
│ └── hogehoge.py
└── tests
└── test_hogehoge.py
pytest-watchの実行結果が以下の様に変わり正常にテストが終了しました。
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-5.1.1, py-1.8.0, pluggy-0.12.0
rootdir: sample_tdd
collected 1 item
sample_tdd/tests/test_hogehoge.py . [100%]
============================== 1 passed in 0.02s ===============================
まとめ
pytest、pytest-watchを使ってのPythonによるテスト駆動開発を実施するための前準備として環境構築を行い、簡単なサンプルプログラムを実行し、正しくテストが実施されることを確認しました。今回記載したpytestの仕様上の規則等についても今後随時追加していきたいと思います。
階層型ディレクトリ構造の場合 (2019/9/26追記)
階層型ディレクトリ構造の場合(以下のソース例)のような場合のモジュールのインポートの方法を追記します。
- 階層型ディレクトリ構造のソースの例
sample_source/
├ calculator
│ ├ __init__.py
│ ├ calculator.py
│ └ modules
│ ├ __init__.py
│ ├ add.py
│ ├ division.py
│ ├ multiple.py
│ └ subordinate.py
├ conftest.py
└ tests
└ calculator
├ test_add.py
├ test_calculator.py
├ test_division.py
├ test_multiple.py
└ test_subordinate.py
test_calculator.py
からcalculator.py
を読み込む場合
from calculator import Calculator
test_add.py
からadd.py
を読み込む場合
from calculator.modules import Add
テストが正しく実行されています。
========================= test session starts =========================
platform linux -- Python 3.7.4, pytest-5.1.1, py-1.8.0, pluggy-0.12.0
rootdir: sample_source
collected 4 items
tests/calculator/test_add.py .. [ 50%]
tests/calculator/test_calculator.py .. [100%]
========================== 4 passed in 0.70s ==========================