概要
テストの前処理・後処理
pytestはPythonのテスティングフレームワークの1つであり、Python標準モジュールに組み込まれているunittestと並んでPythonのテスト実装でよく使われています。
テストコードを実装する際、テストケースによっては前処理(setup)や後処理(teardown)をする場合があります。例えば、前処理としてはサンプルデータ用ファイルを作成したり、ファイル操作用の一時ディレクトリを作成する処理などがあり、後処理としてはテストで使用したデータ、ファイル、ディレクトリを削除する処理などがあります。前処理や後処理を実装することで複雑なテストケースを実装することができます。pytestでは下記のような方法でとテストの前処理や後処理を実装することができます。
- フィクスチャを利用
yield
を使う方法: シンプルで一般的。フィクスチャの前後で処理を行う。request.addfinalizer()
: 柔軟に後処理を追加可能。複数のクリーンアップ処理が必要な場合に有効。
- xUnitスタイルを利用
setup_function(function)
/teardown_function(function)
setup_method(self, method)
/teardown_method(self, method)
setup_class(cls)
/teardown_class(cls)
setup_module(module)
/teardown_module(module)
フィクスチャ(カスタムフィクスチャ)
pytestで通常フィクスチャと言った場合はカスタムフィクスチャを指します。カスタムフィクスチャは、テストの共通処理をまとめて定義したもので、テストで必要なデータやリソースを準備します。デコレータ @pytest.fixture
を使って定義し、テスト関数に渡すことで利用します。
yieldを使う方法
yieldはPythonにおける特殊なキーワードで、関数内で yield
を使用すると、その関数は一度に全ての処理を実行するのではなく、一時停止し、結果を一つずつ返すことができます。yield
は、関数を呼び出した部分に値を返し、その後の処理は呼び出し元が再度制御を戻すまで保留されます。一方、似たキーワードとしてreturn
がありますが、return
は関数が結果を返すとその時点で関数の実行が完全に終了するため以後の処理は続きません。
pytestでは、yield
を使ってテストの前処理を行い、テストの実行が終わると yield
の後に書かれたクリーンアップ処理が実行されます
request.addfinalizer()を使う方法
request はpytest の組み込みフィクスチャの一つでテストに関するさまざまなメタデータ(例えば現在のテスト名やマークなど)や機能を提供します。そしてrequest.addfinalizer() は、その request オブジェクトの一つのメソッドで後処理を登録しテスト終了後にその関数を自動的に実行させることができます。
xUnitスタイル
前述したsetup_
やteardown_
から始まる関数は、古くからあるxUnit系テストフレームワーク(JUnit、unittestなど)で使用される命名規則やテスト構造に由来しています。pytest
でも互換性や利便性のためにサポートされていますが、現在では pytest.fixture
を使ったフィクスチャが、より柔軟で推奨される方法として位置付けられています。こちらの公式サイトでpytestのxUnitスタイルの説明がなされています。
前処理・後処理の実装
前提
pytestの実行環境構築は下記の記事をご参考ください。
フィクスチャ
yieldを使う方法
まずコードの3行目で@pytest.fixture()
デコーダーを記述します。5-8行目で前処理⇒yield
⇒後処理を記述します。そして最後に14行目の引数にデコーダーを付けメソッド名を記述します。
import pytest
@pytest.fixture
def sample_data():
print("前処理:データを準備します")
data = {"key": "value"}
yield data # テスト関数にデータを渡す
print("後処理:クリーンアップします") # テスト後に実行される
def test_example(sample_data):
print(f"テストでデータを利用します: {sample_data}")
# 実行結果
前処理:データを準備します
テストでデータを利用します: {'key': 'value'}
後処理:クリーンアップします
request.addfinalizer()を使う方法
まずコードの3行目で@pytest.fixture()
デコーダーを記述します。4行目の引数にrequest
がありますが、これは組み込みフィクスチャでありrequest.addfinalizer()
を利用する場合は必須になります。5-10行目で前処理と後処理の登録処理を記述します。またsetup_processing
や teardown_processing
は決まったメソッド名ではありません。これらの名前はユーザーが自由に付けることができ、他の名前にしても構いません。最後に14行目の引数にデコーダーを付けメソッド名を記述します。
import pytest
@pytest.fixture()
def setup_processing(request):
# 前処理
print("setup_processing")
# 後処理の登録
def teardown_processing():
print("teardown_processing")
request.addfinalizer(teardown_processing)
def test_hello(setup_processing):
print("hello")
# 実行結果
setup_processing
hello
teardown_processing
ちなみにrequest.addfinalizer()
を複数回呼び出すことで、複数の後処理を順番に登録できます。これらの後処理は、 逆順 で実行されます。つまり最後に登録された関数が最初に実行されることに注意ください。
import pytest
@pytest.fixture()
def setup_processing(request):
print("setup_processing")
def teardown_processing_one():
print("teardown_processing_one")
def teardown_processing_two():
print("teardown_processing_two")
# 複数の後処理を登録
request.addfinalizer(teardown_processing_one)
request.addfinalizer(teardown_processing_two)
def test_hello(setup_processing):
print("hello")
# 実行結果
setup_processing
hello
teardown_processing_two
teardown_processing_one
xUnitスタイル
setup_function(function) / teardown_function(function)
関数単位で毎回同じ前処理・後処理が必要な場合に利用します。引数のfunction
は任意ですが、テスト関数への参照が渡されます。
# test_sample.py
def setup_function(function):
print("setup_function")
def teardown_function(function):
print("teardown_function")
def test_example_one():
print("test_example_one")
def test_example_two():
print("test_example_two")
# 実行結果
setup_function
test_example_one
teardown_function
setup_function
test_example_two
teardown_function
setup_method(self, method) / teardown_method(self, method)
クラス内のメソッド単位で前処理・後処理を行いたい場合に利用します。引数のmethod
は任意ですが、テストメソッドへの参照が渡されます。
# test_sample.py
class TestClassExample:
def setup_method(self, method):
print("setup_method")
def teardown_method(self, method):
print("teardown_method")
def test_example_one(self):
print("test_example_one")
def test_example_two(self):
print("test_example_two")
# 実行結果
setup_method
test_example_one
teardown_method
setup_method
test_example_two
teardown_method
setup_class(cls) / teardown_class(cls)
クラス全体で前処理・後処理を行いたい場合に利用します。引数のcls
は必須で、クラス自体を表します。
# test_sample.py
class TestClassExample:
@classmethod
def setup_class(cls):
print("setup_class")
@classmethod
def teardown_class(cls):
print("teardown_class")
def test_example_one(self):
print("test_example_one")
def test_example_two(self):
print("test_example_two")
# 実行結果
setup_class
test_example_one
test_example_two
teardown_class
setup_module(module) / teardown_module(module)
モジュール全体で前処理・後処理が必要な場合に利用します。引数のmodule
は任意ですが、モジュールへの参照が渡されます。
# test_sample.py
def setup_module(module):
print("setup_module")
def teardown_module(module):
print("teardown_module")
def test_example_one():
print("test_example_one")
def test_example_two():
print("test_example_two")
# 実行結果
setup_module
test_example_one
test_example_two
teardown_module
補足
引数名は自由に変更可能ですが、一般的な名称 (function
、cls
、module
など) を使う方が可読性が高く、他の開発者にも意図が伝わりやすくなります。
まとめ
本記事ではフィクスチャとxUnitスタイルによるpytestの前処理と後処理の実装方法を解説しました。実装したい処理やスコープによっていろいろな実装ができることを学びました。実際の現場ではさまざまな前処理・後処理を実装することになると思いますので、本記事で扱った実装方法を活用してみてくださいね!