pytestのフィクスチャとxUnitスタイルで前処理と後処理を実装しよう!

eyecatch-pytest-fixture-xunit-style
目次

概要

テストの前処理・後処理

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_processingteardown_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

補足

引数名は自由に変更可能ですが、一般的な名称 (functionclsmodule など) を使う方が可読性が高く、他の開発者にも意図が伝わりやすくなります。

まとめ

本記事ではフィクスチャとxUnitスタイルによるpytestの前処理と後処理の実装方法を解説しました。実装したい処理やスコープによっていろいろな実装ができることを学びました。実際の現場ではさまざまな前処理・後処理を実装することになると思いますので、本記事で扱った実装方法を活用してみてくださいね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Hack Luck Labの管理人hakula(ハクラ)です。2012年にSIerに新卒入社し、SE、新規事業、情シスを担当。その後、ITコンサルを経て、現在はバックエンドエンジニア。過去にはC#、SQL Server、JavaScriptで開発を行い、現在はPython、Rest Framework、Postgresql、Linux、AWSなどを使用しています。ノーコードツールやDX関連も興味あり。「技術は価値を生むために使う」ことが信条で、顧客や組織への貢献を重視しています。

目次