🙌

@pytest.fixture 前処理、後処理に引数を渡す

2023/02/07に公開

すみません、少し長くなったので「前処理&後処理&引数を渡す」についてだけ、見たい場合「後処理でも引数を使いたい場合」の箇所に飛んでください。

各テストケースの前に共通の処理をしたい

そんなときに pytest で役立つのはご存知 fixture

@pytest.fixture
def setup():
    # データの初期化など
    table = Table()
    return table

class TestClass:
    def test_code_1(self, setup):
        test_data = "test_data"
        expected = "test_data"
        assert test_data == expected # なんじゃこのテストはsetupいらんやんけ

こんな形で、共通化したい前処理を定義した関数 setup@pytest.fixture をデコレートして使う。
テストを実行する関数 test_code_1 では、以下のように引数に追加するだけでテストの処理に入る前に setup の内容を実行してくれる。

ちなみに、@pytest.fixture(autouse=True) とすれば、 test_code_1() の引数に、 setup を渡さなくても実行される。

@pytest.fixture(autouse=True)
def setup():
    ...

class TestClass:
    def test_code_1(self):
    ...

前処理に加えて、後処理も共通化したい

となったら

@pytest.fixture
def setup():
    # 前処理
    print("テストはじめるよ")

    yield # return -> yield (戻り値があれば yield something)

    # 後処理
    print("テストおわったよ")

class TestClass:
    def test_code_1(self, setup):
        test_data = "test_code_1"
        expected = "test_code_1"

        print("test_code_1")
        assert test_data == expected

    def test_code_2(self, setup):
        test_data = "test_code_2"
        expected = "test_code_2"

        print("test_code_2")
        assert test_data == expected

上記のコードを実行すると、

テストはじめるよ
test_code_1
テストおわったよ
テストはじめるよ
test_code_2
テストおわったよ

と、各テストの前に setupyield より前の部分、

各テストの後に setupyield より後の部分を実行してくれる。

setup() の実行タイミングを変えるには(関数毎じゃなくて、テスト全体で一回とか)

1 つ前のコードは、setup 内の処理が、各テストケース毎に呼び出されているが、それは@pytest.fixtureに渡せるscopeという引数を指定していないから。

scope 実行タイミング default
function テストケース毎
class クラス毎
module テストファイル毎
session テスト全体で 1 回

上記がscopeで指定できる値で、デフォルトは function のため、指定しない場合はテストケース毎に実行される。

とここまでタイトルの、「引数を渡す方法」からいくらか脱線してきた気もしますが、ようやく本題に入ります。

setup() に引数を渡すには

まず、@pytest.fixtureをデコレートした関数に引数を渡す方法はこんな形です。

@pytest.fixture
def setup():
  def _setup(arg):
    # 前処理
    return arg
  return _setup

setupの內部に、_setupという関数を定義して、その中で前処理を記述します。そして、外側のsetup では_setupを返すようにしています。

setup() に引数を渡す

今までさんざん、@pytest.fixtureをデコレートして前処理、前処理言ってきましたが、前処理に限らず共通化したいことに使えるので、例えば引数の数字をソートする関数に@pytest.fixtureをつけて、引数を渡す例を書いてみます。


@pytest.fixture
def sort_desc():
  def _sort_desc(arg1, arg2, arg3):
    return sorted([arg1, arg2, arg3], reverse=True)
  return _sort_desc

class TestClass:
  def test_code_1(self, sort_desc):
    test_data = sort_desc(1, 2, 3)
    expected = [3, 2, 1]
    assert test_data == expected

  def test_code_2(self, sort_desc):
    test_data = sort_desc(-1, 3, 100)
    expected = [100, 3, -1]
    assert test_data == expected

テストケースから呼び出すときには、_sort_desc ではなく、sort_descを呼び出せば良いです。

前処理&後処理&引数を渡す

本当に最後に、共通の前処理、後処理をしたい、そして引数を渡したい場合について、サンプルコードを書いてみます。

先程出てきたクソサンプルコードを再掲

@pytest.fixture
def setup():
    # 前処理
    print("テストはじめるよ")

    yield # return -> yield (戻り値があれば yield something)

    # 後処理
    print("テストおわったよ")

class TestClass:
    def test_code_1(self, setup):
        test_data = "test_code_1"
        expected = "test_code_1"

        print("test_code_1")
        assert test_data == expected

    def test_code_2(self, setup):
        test_data = "test_code_2"
        expected = "test_code_2"

        print("test_code_2")
        assert test_data == expected

前処理で”テストはじめるよ”と出力しているので、引数を渡して、テストケース名も出力するようにします。

@pytest.fixture
def setup():
    # 內部に引数を持つ関数を追加

    def _setup(arg):
        # 前処理
        print(f"テストはじめるよ test名: {arg}")
        return

    yield _setup

    # 後処理
    print("テストおわったよ")


class TestClass:
    def test_code_1(self, setup):
        setup("test_code_1")

        test_data = "test_code_1"
        expected = "test_code_1"

        assert test_data == expected

    def test_code_2(self, setup):
        setup("test_code_2")

        test_data = "test_code_2"
        expected = "test_code_2"

        assert test_data == expected

これで、今何のテストを実行しているかわかるようになりました。
めでたしめでたし

後処理でも引数を使いたい場合

後処理でも引数を使いたい場合は

@pytest.fixture
def setup():

    args = [] # 後処理で使う引数を格納するリスト

    def _setup(arg1):
        # 前処理
        args.append(arg1) # 引数をリストに格納
        print(f"テストはじめるよ test名: {arg1}")
        return

    yield _setup

    # 後処理
    for arg in args:
        print(f"テストおわったよ test名: {arg}")

もっと良い方法がありそうですが、とりあえずこれで実現できるはずです。

まとめ

今更ながら、後処理もするのに関数名がsetupっていけてないですよね。
そしてサンプルコードが総じてひどくて申し訳ないです。が抽象化して、scopeもうまく活用してやって頂けたら幸いです。
そして、困ったら(いや最初から)公式ドキュメントを読んでください。
https://docs.pytest.org/en/7.1.x/how-to/fixtures.html
読んでくれてありがとうございました。

Discussion