※記事内に商品プロモーションを含むことがあります。
はじめに
Pythonでパッケージを作成するとき、pytestによる自動テストに向いたディレクトリ構成をサンプルと合わせて解説します。pytestはPythonコードのテストを自動化するライブラリです。
pytestの基本的な使い方は以下の記事にまとめています。
pytestを使ったPythonのテスト – Helve Tech Blog
この記事で検証した環境は以下の通りです。
- OS: Windows 10 Home
- Python 3.11.6
- pytest 7.4.3
以下のコマンドでpytestをインストールします。
この記事はPython Advent Calendar 2023 (Qiita) 11日目の記事です。
ディレクトリ構成
ディレクトリ構成の例を以下に示します。<my_project>
は任意のパッケージ名に変更します。
<my_project>/
<my_project>/
__init__.py
<my_project>.py
*.py
tests/
__init__.py
test_<my_project>.py
test_*.py
<my_project>/<my_project>/
フォルダの下にパッケージのプログラムを置きます。また、<my_project>/tests/
フォルダの下には、テスト用のプログラムを置きます。
pytestを実行するに当たって、tests
フォルダ直下の__init__.py
が必要になります。また、*.py
やtest_*.py
は、必要に応じて追加する任意の名前のモジュールです。
パッケージの例
簡単なパッケージを例として示します。ここではパッケージ名をfoo
とします。
foo/
foo/
__init__.py
foo.py
tests/
__init__.py
test_foo.py
4つのファイルの内容を以下に示します。
foo/foo/__init__.py
の書き方はいくつか方法がありますが、以下とします。このファイルによって、プロジェクトのルートフォルダfoo/
直下でpytest
コマンドを実行したときに、foo/foo/foo.py
をインポートできるようになります。
foo/foo/foo.py
(実際のプログラム)の中身は以下とします。2つの変数の和を返す単純な関数です。
1
2
|
def add(x, y):
return x+y
|
一方、foo/tests/__init__.py
の中身は空とします。このファイルを置くことによって、pytestがtests
フォルダをパスに追加してくれます。
foo/tests/test_foo.py
はfoo.py
をテストするためのファイルです。ファイル名の先頭にtest
を付けることによって、テスト内容を定義したファイルであることをpytestに認識させます。中身は以下とします。import foo
を記述して、foo.py
をインポートすることに注意してください。
1
2
3
4
|
import foo
def test_add():
assert foo.add(1, 2) == 3
|
pytestの実行
pytestはコマンドプロンプトまたはPowerShellから実行します。プロジェクトのルートフォルダfoo/
直下で、pytest
コマンドを実行します。
1
2
3
4
5
6
7
8
9
10
|
foo> pytest
======================= test session starts ========================
platform win32 -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0
rootdir: /XXX/foo/
plugins: html-4.1.1, metadata-3.0.0
collected 1 item
tests\test_foo.py . [100%]
======================== 1 passed in 0.03s =========================
|
pytest実行時のエラーについて
pytest
コマンドを実行するときに発生しがちなエラーを以下に示します。
AttributeError
foo/foo/__init__.py
を置いていない場合や、このファイルの中身が空の場合、以下のようにAttributeError
が発生します。foo/foo/__init__.py
のファイル名や中身が間違っていないか等、確認して下さい。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
foo> pytest
======================= test session starts ========================
platform win32 -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0
rootdir: /XXX/foo
plugins: html-4.1.1, metadata-3.0.0
collected 1 item
tests\test_foo.py F [100%]
============================= FAILURES =============================
_____________________________ test_add _____________________________
def test_add():
> assert foo.add(1, 2) == 3
E AttributeError: module 'foo' has no attribute 'add'
tests\test_foo.py:6: AttributeError
===================== short test summary info ======================
FAILED tests/test_foo.py::test_add - AttributeError: module 'foo' has no attribute 'add'
======================== 1 failed in 0.20s =========================
|
ModuleNotFoundError
foo/tests/__init__.py
を置いていない場合、以下のようにModuleNotFoundError
が発生します。foo/tests/__init__.py
のファイル名が間違っていないか等、確認して下さい。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
foo> pytest
======================= test session starts ========================
platform win32 -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0
rootdir: /XXX/foo/
plugins: html-4.1.1, metadata-3.0.0
collected 0 items / 1 error
============================== ERRORS ==============================
________________ ERROR collecting tests/test_foo.py ________________
ImportError while importing test module 'XXX/foo/tests/test_foo.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
C:\Users\helve\AppData\Local\Programs\Python\Python311\Lib\importlib\__init__.py:126: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests\test_foo.py:2: in <module>
import foo
E ModuleNotFoundError: No module named 'foo'
===================== short test summary info ======================
ERROR tests/test_foo.py
!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!
========================= 1 error in 0.16s =========================
|
参考