フィクスチャの使い方

フィクスチャの「リクエスト」

基本的なレベルでは、テスト関数は必要なフィクスチャを引数として宣言することでリクエストします。

pytest がテストを実行するとき、そのテスト関数のシグネチャ内のパラメータを確認し、それらのパラメータと同じ名前のフィクスチャを検索します。 pytest がそれらを見つけると、それらのフィクスチャを実行し、返されたもの (もしあれば) をキャプチャし、それらのオブジェクトを引数としてテスト関数に渡します。

簡単な例

import pytest


class Fruit:
    def __init__(self, name):
        self.name = name
        self.cubed = False

    def cube(self):
        self.cubed = True


class FruitSalad:
    def __init__(self, *fruit_bowl):
        self.fruit = fruit_bowl
        self._cube_fruit()

    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()


# Arrange
@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]


def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)

    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

この例では、test_fruit_saladfruit_bowl を「リクエスト」します (つまり、def test_fruit_salad(fruit_bowl):) 。 pytest がこれを確認すると、fruit_bowl フィクスチャ関数を実行し、返されたオブジェクトを test_fruit_saladfruit_bowl 引数として渡します。

手動で行うと大まかに次のようになります:

def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]


def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)

    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)


# Arrange
bowl = fruit_bowl()
test_fruit_salad(fruit_bowl=bowl)

フィクスチャは他のフィクスチャを リクエスト できます

pytest の最大の強みの一つは、その非常に柔軟なフィクスチャシステムです。 これにより、テストの複雑な要件をよりシンプルで整理された関数にまとめることができ、それぞれが依存しているものを記述するだけで済みます。 これについては後ほど詳しく説明しますが、ここではフィクスチャが他のフィクスチャをどのように使用できるかを示す簡単な例を紹介します:

# contents of test_append.py
import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]

これは上記の例と同じですが、ほとんど変更されていないことに注意してください。 pytest のフィクスチャはテストと同様にフィクスチャを リクエスト します。 テストに適用されるすべての リクエスト ルールがフィクスチャにも適用されます。 この例を手動で行うと次のようになります:

def first_entry():
    return "a"


def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


entry = first_entry()
the_list = order(first_entry=entry)
test_string(order=the_list)

フィクスチャは再利用可能です

pytest のフィクスチャシステムを非常に強力にしている要素の一つは、通常の関数のように何度も再利用できる汎用的なセットアップステップを定義できることです。 異なる2つのテストが同じフィクスチャをリクエストし、pytest がそれぞれのテストに対してそのフィクスチャから独自の結果を提供することができます。

これは、テストが互いに影響を受けないようにするために非常に役立ちます。 このシステムを使用して、各テストが独自の新しいデータバッチを取得し、クリーンな状態から開始することを確認できるため、一貫した再現可能な結果を提供できます。

これがどのように役立つかの例を示します:

# contents of test_append.py
import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


def test_int(order):
    # Act
    order.append(2)

    # Assert
    assert order == ["a", 2]

ここでは、各テストにその list オブジェクトの独自のコピーが与えられています。 つまり、order フィクスチャが2回実行されていることを意味します (first_entry フィクスチャも同様です) 。 これを手動で行うと次のようになります:

def first_entry():
    return "a"


def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


def test_int(order):
    # Act
    order.append(2)

    # Assert
    assert order == ["a", 2]


entry = first_entry()
the_list = order(first_entry=entry)
test_string(order=the_list)

entry = first_entry()
the_list = order(first_entry=entry)
test_int(order=the_list)

テスト/フィクスチャは一度に複数のフィクスチャを リクエスト できます

テストとフィクスチャは、一度に単一のフィクスチャを リクエスト することに限定されません。 好きなだけリクエストできます。 これを示す別の簡単な例を示します:

# contents of test_append.py
import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def second_entry():
    return 2


# Arrange
@pytest.fixture
def order(first_entry, second_entry):
    return [first_entry, second_entry]


# Arrange
@pytest.fixture
def expected_list():
    return ["a", 2, 3.0]


def test_string(order, expected_list):
    # Act
    order.append(3.0)

    # Assert
    assert order == expected_list

フィクスチャはテストごとに複数回 リクエスト できます (戻り値はキャッシュされます)

フィクスチャは同じテスト中に複数回 リクエスト することもでき、pytest はそのテストのためにそれらを再実行しません。 これにより、依存している複数のフィクスチャ (およびテスト自体でも) でフィクスチャを リクエスト しても、それらのフィクスチャが複数回実行されることはありません。

# contents of test_append.py
import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def order():
    return []


# Act
@pytest.fixture
def append_first(order, first_entry):
    return order.append(first_entry)


def test_string_only(append_first, order, first_entry):
    # Assert
    assert order == [first_entry]

もし リクエスト されたフィクスチャがテスト中にリクエストされるたびに1回実行された場合、このテストは失敗します。 なぜなら、append_firsttest_string_only の両方が order を空のリスト (つまり []) として見るからです。 しかし、order の戻り値が最初に呼び出された後にキャッシュされたため (実行時に発生した副作用も含む) 、テストと append_first の両方が同じオブジェクトを参照しており、テストは append_first がそのオブジェクトに与えた影響を確認しました。

自動使用フィクスチャ (リクエストする必要のないフィクスチャ)

すべてのテストが依存するフィクスチャ (または複数のフィクスチャ) を持ちたい場合があります。 「自動使用」フィクスチャは、すべてのテストが自動的にそれらを リクエスト する便利な方法です。 これにより、多くの冗長な リクエスト を省くことができ、さらに高度なフィクスチャの使用法を提供することもできます (詳細は後述します) 。

フィクスチャのデコレータに autouse=True を渡すことで、フィクスチャを自動使用フィクスチャにすることができます。 これがどのように使用できるかの簡単な例を示します:

# contents of test_append.py
import pytest


@pytest.fixture
def first_entry():
    return "a"


@pytest.fixture
def order(first_entry):
    return []


@pytest.fixture(autouse=True)
def append_first(order, first_entry):
    return order.append(first_entry)


def test_string_only(order, first_entry):
    assert order == [first_entry]


def test_string_and_int(order, first_entry):
    order.append(2)
    assert order == [first_entry, 2]

この例では、append_first フィクスチャは自動使用フィクスチャです。 自動的に発生するため、どちらのテストもそれによって影響を受けますが、どちらのテストもそれを リクエスト していません。 それは リクエスト できないという意味ではなく、単に 必要ではない ということです。

スコープ:クラス、モジュール、パッケージ、またはセッション間でフィクスチャを共有する

ネットワークアクセスを必要とするフィクスチャは接続性に依存し、通常、作成に時間がかかります。 前の例を拡張して、@pytest.fixture 呼び出しに scope="module" パラメータを追加することで、既存の SMTP サーバーへの接続を作成する smtp_connection フィクスチャ関数がテスト モジュール ごとに1回だけ呼び出されるようにします (デフォルトではテスト 関数 ごとに1回呼び出されます) 。 テストモジュール内の複数のテスト関数は、それぞれ同じ smtp_connection フィクスチャインスタンスを受け取るため、時間を節約できます。 scope の可能な値は、functionclassmodulepackage、または session です。

次の例では、フィクスチャ関数を別の conftest.py ファイルに配置し、ディレクトリ内の複数のテストモジュールからフィクスチャ関数にアクセスできるようにします:

# content of conftest.py
import smtplib

import pytest


@pytest.fixture(scope="module")
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
# content of test_module.py


def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert b"smtp.gmail.com" in msg
    assert 0  # for demo purposes


def test_noop(smtp_connection):
    response, msg = smtp_connection.noop()
    assert response == 250
    assert 0  # for demo purposes

ここで、test_ehlosmtp_connection フィクスチャ値を必要とします。 pytest は @pytest.fixture でマークされた smtp_connection フィクスチャ関数を発見して呼び出します。 テストの実行は次のようになります:

$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

test_module.py FF                                                    [100%]

================================= FAILURES =================================
________________________________ test_ehlo _________________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0001>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b"smtp.gmail.com" in msg
>       assert 0  # for demo purposes
E       assert 0

test_module.py:7: AssertionError
________________________________ test_noop _________________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0001>

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
>       assert 0  # for demo purposes
E       assert 0

test_module.py:13: AssertionError
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo - assert 0
FAILED test_module.py::test_noop - assert 0
============================ 2 failed in 0.12s =============================

2つの assert 0 が失敗しているのがわかりますが、さらに重要なのは、pytest がトレースバックで受け取った引数の値を表示するため、2つのテスト関数に まったく同じ smtp_connection オブジェクトが渡されたこともわかります。 その結果、smtp_connection を使用する2つのテスト関数は、同じインスタンスを再利用するため、単一のテストと同じくらい速く実行されます。

セッションスコープの smtp_connection インスタンスを持ちたい場合は、単にそれを宣言することができます:

@pytest.fixture(scope="session")
def smtp_connection():
    # the returned fixture value will be shared for
    # all tests requesting it
    ...

フィクスチャのスコープ

フィクスチャはテストによって最初にリクエストされたときに作成され、その scope に基づいて破棄されます:

  • function:デフォルトのスコープで、フィクスチャはテストの終了時に破棄されます。

  • class:フィクスチャはクラス内の最後のテストの後片付け中に破棄されます。

  • module:フィクスチャはモジュール内の最後のテストの後片付け中に破棄されます。

  • package:フィクスチャは、フィクスチャが定義されているパッケージ内の最後のテストの後片付け中に破棄されます。 サブパッケージおよびその中のサブディレクトリも含まれます。

  • session:フィクスチャはテストセッションの終了時に破棄されます。

注釈

Pytest は一度に1つのフィクスチャインスタンスしかキャッシュしないため、パラメータ化されたフィクスチャを使用する場合、pytest は指定されたスコープ内でフィクスチャを複数回呼び出すことがあります。

動的スコープ

Added in version 5.2.

場合によっては、コードを変更せずにフィクスチャのスコープを変更したいことがあります。 そのためには、scope に呼び出し可能なオブジェクトを渡します。 呼び出し可能なオブジェクトは有効なスコープを持つ文字列を返す必要があり、フィクスチャの定義中に1回だけ実行されます。 2つのキーワード引数を使用して呼び出されます - fixture_name は文字列として、config は構成オブジェクトとして。

これは、Docker コンテナの生成など、セットアップに時間がかかるフィクスチャを扱う場合に特に役立ちます。 コマンドライン引数を使用して、異なる環境の生成されたコンテナのスコープを制御できます。 以下の例を参照してください。

def determine_scope(fixture_name, config):
    if config.getoption("--keep-containers", None):
        return "session"
    return "function"


@pytest.fixture(scope=determine_scope)
def docker_container():
    yield spawn_container()

後片付け/クリーンアップ (別名フィクスチャの終了)

テストを実行するとき、他のテストに影響を与えないように後片付けを行うことを確認したい (また、システムを膨らませるテストデータの山を残さないようにするため) 。 pytest のフィクスチャは非常に便利な後片付けシステムを提供しており、各フィクスチャが自分自身をクリーンアップするために必要な特定の手順を定義できます。

このシステムは2つの方法で活用できます。

2. 直接ファイナライザを追加する

yield フィクスチャはよりクリーンでわかりやすいオプションと見なされますが、別の選択肢もあります。 それは、テストの request-context オブジェクトに「ファイナライザ」関数を直接追加することです。 yield フィクスチャと同様の結果をもたらしますが、少し冗長になります。

このアプローチを使用するには、後片付けコードを追加する必要があるフィクスチャで request-context オブジェクトをリクエストし (他のフィクスチャをリクエストするのと同様に) 、その後片付けコードを含む呼び出し可能なオブジェクトをその addfinalizer メソッドに渡します。

ただし注意が必要です。 pytest はファイナライザが追加されると、そのフィクスチャがファイナライザを追加した後に例外を発生させた場合でも、そのファイナライザを実行します。 そのため、必要のないときにファイナライザコードを実行しないようにするために、フィクスチャが後片付けする必要がある何かを行った場合にのみファイナライザを追加します。

前の例を addfinalizer メソッドを使用してどのように見えるかを示します:

# content of test_emaillib.py
from emaillib import Email, MailAdminClient

import pytest


@pytest.fixture
def mail_admin():
    return MailAdminClient()


@pytest.fixture
def sending_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)


@pytest.fixture
def receiving_user(mail_admin, request):
    user = mail_admin.create_user()

    def delete_user():
        mail_admin.delete_user(user)

    request.addfinalizer(delete_user)
    return user


@pytest.fixture
def email(sending_user, receiving_user, request):
    _email = Email(subject="Hey!", body="How's it going?")
    sending_user.send_email(_email, receiving_user)

    def empty_mailbox():
        receiving_user.clear_mailbox()

    request.addfinalizer(empty_mailbox)
    return _email


def test_email_received(receiving_user, email):
    assert email in receiving_user.inbox

yield フィクスチャよりも少し長く、少し複雑ですが、困ったときに役立ついくつかのニュアンスを提供します。

$ pytest -q test_emaillib.py
.                                                                    [100%]
1 passed in 0.12s

ファイナライザの順序に関する注意

ファイナライザは先入れ後出しの順序で実行されます。 yield フィクスチャの場合、最初に実行される後片付けコードは最も右のフィクスチャ、つまり最後のテストパラメータからです。

# content of test_finalizers.py
import pytest


def test_bar(fix_w_yield1, fix_w_yield2):
    print("test_bar")


@pytest.fixture
def fix_w_yield1():
    yield
    print("after_yield_1")


@pytest.fixture
def fix_w_yield2():
    yield
    print("after_yield_2")
$ pytest -s test_finalizers.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_finalizers.py test_bar
.after_yield_2
after_yield_1


============================ 1 passed in 0.12s =============================

ファイナライザの場合、最初に実行されるフィクスチャは request.addfinalizer の最後の呼び出しです。

# content of test_finalizers.py
from functools import partial
import pytest


@pytest.fixture
def fix_w_finalizers(request):
    request.addfinalizer(partial(print, "finalizer_2"))
    request.addfinalizer(partial(print, "finalizer_1"))


def test_bar(fix_w_finalizers):
    print("test_bar")
$ pytest -s test_finalizers.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_finalizers.py test_bar
.finalizer_1
finalizer_2


============================ 1 passed in 0.12s =============================

これは、yield フィクスチャが背後で addfinalizer を使用するためです。 フィクスチャが実行されると、addfinalizer はジェネレータを再開する関数を登録し、それが後片付けコードを呼び出します。

安全な後片付け

pytest のフィクスチャシステムは 非常に 強力ですが、それでもコンピュータによって実行されているため、投げかけたすべてのものを安全に後片付けする方法を見つけることはできません。 注意しないと、間違った場所でのエラーがテストの残骸を残し、それがさらに問題を引き起こす可能性があります。

たとえば、次のテストを考えてみましょう (上記のメールの例に基づいています) :

# content of test_emaillib.py
from emaillib import Email, MailAdminClient

import pytest


@pytest.fixture
def setup():
    mail_admin = MailAdminClient()
    sending_user = mail_admin.create_user()
    receiving_user = mail_admin.create_user()
    email = Email(subject="Hey!", body="How's it going?")
    sending_user.send_email(email, receiving_user)
    yield receiving_user, email
    receiving_user.clear_mailbox()
    mail_admin.delete_user(sending_user)
    mail_admin.delete_user(receiving_user)


def test_email_received(setup):
    receiving_user, email = setup
    assert email in receiving_user.inbox

このバージョンははるかにコンパクトですが、読みづらく、非常に説明的なフィクスチャ名がなく、フィクスチャのいずれも簡単に再利用できません。

さらに深刻な問題もあります。 セットアップのいずれかのステップで例外が発生した場合、後片付けコードは実行されません。

1つのオプションは、yield フィクスチャの代わりに addfinalizer メソッドを使用することですが、それは非常に複雑で維持が難しくなる可能性があります (そしてもはやコンパクトではありません) 。

$ pytest -q test_emaillib.py
.                                                                    [100%]
1 passed in 0.12s

安全なフィクスチャ構造

最も安全でシンプルなフィクスチャ構造は、各フィクスチャを1つの状態変更アクションのみに制限し、それらを後片付けコードと一緒にバンドルすることを必要とします。 これは the email examples above で示されています。

状態変更操作が失敗しても状態を変更する可能性はほとんどありません。 これらの操作のほとんどは transaction ベースであるためです (少なくとも状態が残る可能性のあるテストレベルでは) 。 したがって、成功した状態変更アクションが別のフィクスチャ関数に移動され、他の潜在的に失敗する状態変更アクションから分離されることを確認すれば、テスト環境を見つけたままの状態で残す可能性が最も高くなります。

例として、ログインページを持つウェブサイトがあり、ユーザーを生成できる管理 API にアクセスできるとします。 テストのために、次のことを行いたい:

  1. その管理 API を通じてユーザーを作成する

  2. Selenium を使用してブラウザを起動する

  3. サイトのログインページに移動する

  4. 作成したユーザーとしてログインする

  5. ランディングページのヘッダーにその名前があることを確認する

そのユーザーをシステムに残したくないし、そのブラウザセッションを実行し続けたくもないので、それらを作成するフィクスチャが自分自身をクリーンアップすることを確認したい。

その例を示します:

注釈

この例では、特定のフィクスチャ (つまり base_url および admin_credentials) が他の場所に存在することが示唆されています。 したがって、今のところ、それらが存在すると仮定し、それらを見ていないだけです。

from uuid import uuid4
from urllib.parse import urljoin

from selenium.webdriver import Chrome
import pytest

from src.utils.pages import LoginPage, LandingPage
from src.utils import AdminApiClient
from src.utils.data_types import User


@pytest.fixture
def admin_client(base_url, admin_credentials):
    return AdminApiClient(base_url, **admin_credentials)


@pytest.fixture
def user(admin_client):
    _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
    admin_client.create_user(_user)
    yield _user
    admin_client.delete_user(_user)


@pytest.fixture
def driver():
    _driver = Chrome()
    yield _driver
    _driver.quit()


@pytest.fixture
def login(driver, base_url, user):
    driver.get(urljoin(base_url, "/login"))
    page = LoginPage(driver)
    page.login(user)


@pytest.fixture
def landing_page(driver, login):
    return LandingPage(driver)


def test_name_on_landing_page_after_login(landing_page, user):
    assert landing_page.header == f"Welcome, {user.name}!"

依存関係の配置方法から、user フィクスチャが driver フィクスチャの前に実行されるかどうかは不明です。 しかし、それは問題ありません。 これらはアトミック操作であり、どちらが最初に実行されるかは関係ありません。 なぜなら、テストのイベントの順序は依然として linearizable だからです。 しかし、重要 なのは、どちらが最初に実行されても、片方が例外を発生させた場合、もう片方は何も残さないということです。 driveruser の前に実行され、user が例外を発生させた場合、ドライバーは終了し、ユーザーは作成されません。 そして、driver が例外を発生させた場合、ドライバーは開始されず、ユーザーも作成されません。

複数の assert ステートメントを安全に実行する

すべてのセットアップを行った後に複数のアサートを実行したい場合があります。 これは、より複雑なシステムでは、単一のアクションが複数の動作を引き起こす可能性があるためです。 pytest にはこれを処理する便利な方法があり、これまでに説明したことの多くを組み合わせています。

必要なのは、より大きなスコープにステップアップし、act ステップを自動使用フィクスチャとして定義し、最後にすべてのフィクスチャがその高レベルのスコープを対象としていることを確認することです。

an example from above を引き出し、少し調整しましょう。 ヘッダーにウェルカムメッセージがあるかどうかを確認するだけでなく、サインアウトボタンとユーザープロファイルへのリンクも確認したいとします。

すべてのステップを再度繰り返すことなく、複数のアサートを実行できるように、それをどのように構築できるかを見てみましょう。

注釈

この例では、特定のフィクスチャ (つまり base_url および admin_credentials) が他の場所に存在することが示唆されています。 したがって、今のところ、それらが存在すると仮定し、それらを見ていないだけです。

# contents of tests/end_to_end/test_login.py
from uuid import uuid4
from urllib.parse import urljoin

from selenium.webdriver import Chrome
import pytest

from src.utils.pages import LoginPage, LandingPage
from src.utils import AdminApiClient
from src.utils.data_types import User


@pytest.fixture(scope="class")
def admin_client(base_url, admin_credentials):
    return AdminApiClient(base_url, **admin_credentials)


@pytest.fixture(scope="class")
def user(admin_client):
    _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
    admin_client.create_user(_user)
    yield _user
    admin_client.delete_user(_user)


@pytest.fixture(scope="class")
def driver():
    _driver = Chrome()
    yield _driver
    _driver.quit()


@pytest.fixture(scope="class")
def landing_page(driver, login):
    return LandingPage(driver)


class TestLandingPageSuccess:
    @pytest.fixture(scope="class", autouse=True)
    def login(self, driver, base_url, user):
        driver.get(urljoin(base_url, "/login"))
        page = LoginPage(driver)
        page.login(user)

    def test_name_in_header(self, landing_page, user):
        assert landing_page.header == f"Welcome, {user.name}!"

    def test_sign_out_button(self, landing_page):
        assert landing_page.sign_out_button.is_displayed()

    def test_profile_link(self, landing_page, user):
        profile_href = urljoin(base_url, f"/profile?id={user.profile_id}")
        assert landing_page.profile_link.get_attribute("href") == profile_href

メソッドが署名で self を形式的に参照しているだけであることに注意してください。 状態は unittest.TestCase フレームワークのように実際のテストクラスに結び付けられていません。 すべては pytest フィクスチャシステムによって管理されています。

各メソッドは、順序を気にせずに実際に必要なフィクスチャをリクエストするだけで済みます。 これは、act フィクスチャが自動使用フィクスチャであり、他のすべてのフィクスチャがその前に実行されることを確認したためです。 状態の変更はもう必要ないため、テストは他のテストの邪魔をするリスクなしに、好きなだけ状態を変更しないクエリを自由に行うことができます。

login フィクスチャもクラス内で定義されています。 モジュール内の他のすべてのテストが成功したログインを期待しているわけではなく、act は別のテストクラスに対して少し異なる方法で処理する必要がある場合があります。 たとえば、不正な資格情報を送信する別のテストシナリオを作成したい場合は、次のようなものをテストファイルに追加して処理できます:

class TestLandingPageBadCredentials:
    @pytest.fixture(scope="class")
    def faux_user(self, user):
        _user = deepcopy(user)
        _user.password = "badpass"
        return _user

    def test_raises_bad_credentials_exception(self, login_page, faux_user):
        with pytest.raises(BadCredentialsException):
            login_page.login(faux_user)

フィクスチャはリクエストしているテストコンテキストを内省できます

フィクスチャ関数は request オブジェクトを受け入れて、「リクエストしている」テスト関数、クラス、またはモジュールコンテキストを内省できます。 前の smtp_connection フィクスチャの例をさらに拡張して、フィクスチャを使用するテストモジュールからオプションのサーバー URL を読み取ります:

# content of conftest.py
import smtplib

import pytest


@pytest.fixture(scope="module")
def smtp_connection(request):
    server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    smtp_connection = smtplib.SMTP(server, 587, timeout=5)
    yield smtp_connection
    print(f"finalizing {smtp_connection} ({server})")
    smtp_connection.close()

request.module 属性を使用して、テストモジュールからオプションで smtpserver 属性を取得します。 再度実行するだけで、ほとんど何も変わりません:

$ pytest -s -q --tb=no test_module.py
FFfinalizing <smtplib.SMTP object at 0xdeadbeef0002> (smtp.gmail.com)

========================= short test summary info ==========================
FAILED test_module.py::test_ehlo - assert 0
FAILED test_module.py::test_noop - assert 0
2 failed in 0.12s

モジュールの名前空間にサーバー URL を実際に設定する別のテストモジュールをすばやく作成しましょう:

# content of test_anothersmtp.py

smtpserver = "mail.python.org"  # will be read by smtp fixture


def test_showhelo(smtp_connection):
    assert 0, smtp_connection.helo()

実行中:

$ pytest -qq --tb=short test_anothersmtp.py
F                                                                    [100%]
================================= FAILURES =================================
______________________________ test_showhelo _______________________________
test_anothersmtp.py:6: in test_showhelo
    assert 0, smtp_connection.helo()
E   AssertionError: (250, b'mail.python.org')
E   assert 0
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef0003> (mail.python.org)
========================= short test summary info ==========================
FAILED test_anothersmtp.py::test_showhelo - AssertionError: (250, b'mail....

ほら! smtp_connection フィクスチャ関数がモジュールの名前空間からメールサーバー名を取得しました。

マーカーを使用してデータをフィクスチャに渡す

request オブジェクトを使用して、フィクスチャはテスト関数に適用されるマーカーにもアクセスできます。 これは、テストからフィクスチャにデータを渡すのに役立ちます:

import pytest


@pytest.fixture
def fixt(request):
    marker = request.node.get_closest_marker("fixt_data")
    if marker is None:
        # Handle missing marker in some way...
        data = None
    else:
        data = marker.args[0]

    # Do something with the data
    return data


@pytest.mark.fixt_data(42)
def test_fixt(fixt):
    assert fixt == 42

フィクスチャとしてのファクトリ

「フィクスチャとしてのファクトリ」パターンは、フィクスチャの結果が単一のテストで複数回必要な状況で役立ちます。 データを直接返すのではなく、フィクスチャはデータを生成する関数を返します。 この関数はテストで複数回呼び出すことができます。

ファクトリには必要に応じてパラメータを指定できます:

@pytest.fixture
def make_customer_record():
    def _make_customer_record(name):
        return {"name": name, "orders": []}

    return _make_customer_record


def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")

ファクトリによって作成されたデータの管理が必要な場合、フィクスチャがそれを処理できます:

@pytest.fixture
def make_customer_record():
    created_records = []

    def _make_customer_record(name):
        record = models.Customer(name=name, orders=[])
        created_records.append(record)
        return record

    yield _make_customer_record

    for record in created_records:
        record.destroy()


def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")

フィクスチャのパラメータ化

フィクスチャ関数はパラメータ化でき、その場合、複数回呼び出され、各回に依存するテストセット、つまりこのフィクスチャに依存するテストを実行します。 テスト関数は通常、再実行を意識する必要はありません。 フィクスチャのパラメータ化は、複数の方法で構成できるコンポーネントの包括的な機能テストを作成するのに役立ちます。

前の例を拡張して、フィクスチャに2つの smtp_connection フィクスチャインスタンスを作成するフラグを立てることができ、これによりフィクスチャを使用するすべてのテストが2回実行されます。 フィクスチャ関数は、特別な request オブジェクトを介して各パラメータにアクセスします:

# content of conftest.py
import smtplib

import pytest


@pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
    yield smtp_connection
    print(f"finalizing {smtp_connection}")
    smtp_connection.close()

主な変更点は、@pytest.fixtureparams を宣言することで、フィクスチャ関数が実行される値のリストを作成し、request.param を介して値にアクセスできるようにすることです。 テスト関数のコードを変更する必要はありません。 では、もう一度実行してみましょう:

$ pytest -q test_module.py
FFFF                                                                 [100%]
================================= FAILURES =================================
________________________ test_ehlo[smtp.gmail.com] _________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0004>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b"smtp.gmail.com" in msg
>       assert 0  # for demo purposes
E       assert 0

test_module.py:7: AssertionError
________________________ test_noop[smtp.gmail.com] _________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0004>

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
>       assert 0  # for demo purposes
E       assert 0

test_module.py:13: AssertionError
________________________ test_ehlo[mail.python.org] ________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0005>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
>       assert b"smtp.gmail.com" in msg
E       AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'

test_module.py:6: AssertionError
-------------------------- Captured stdout setup ---------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef0004>
________________________ test_noop[mail.python.org] ________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef0005>

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
>       assert 0  # for demo purposes
E       assert 0

test_module.py:13: AssertionError
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef0005>
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo[smtp.gmail.com] - assert 0
FAILED test_module.py::test_noop[smtp.gmail.com] - assert 0
FAILED test_module.py::test_ehlo[mail.python.org] - AssertionError: asser...
FAILED test_module.py::test_noop[mail.python.org] - assert 0
4 failed in 0.12s

2つのテスト関数がそれぞれ異なる smtp_connection インスタンスに対して2回実行されたことがわかります。 また、mail.python.org 接続では、2番目のテストが test_ehlo で失敗します。 到着したサーバー文字列とは異なる文字列が予期されるためです。

pytest は、パラメータ化されたフィクスチャ内の各フィクスチャ値のテスト ID となる文字列を構築します。 たとえば、上記の例では test_ehlo[smtp.gmail.com] および test_ehlo[mail.python.org] です。 これらの ID は -k と一緒に使用して実行する特定のケースを選択でき、1つが失敗した場合に特定のケースを識別します。 --collect-only オプションで pytest を実行すると、生成された ID が表示されます。

数値、文字列、ブール値、および None は、テスト ID で通常の文字列表現が使用されます。 他のオブジェクトの場合、pytest は引数名に基づいて文字列を作成します。 特定のフィクスチャ値のテスト ID で使用される文字列を ids キーワード引数を使用してカスタマイズすることができます:

# content of test_ids.py
import pytest


@pytest.fixture(params=[0, 1], ids=["spam", "ham"])
def a(request):
    return request.param


def test_a(a):
    pass


def idfn(fixture_value):
    if fixture_value == 0:
        return "eggs"
    else:
        return None


@pytest.fixture(params=[0, 1], ids=idfn)
def b(request):
    return request.param


def test_b(b):
    pass

上記は、ids が使用する文字列のリストであるか、フィクスチャ値で呼び出され、使用する文字列を返す必要がある関数であるかのいずれかであることを示しています。 後者の場合、関数が None を返すと、pytest の自動生成された ID が使用されます。

上記のテストを実行すると、次のテスト ID が使用されます:

$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 12 items

<Dir fixtures.rst-225>
  <Module test_anothersmtp.py>
    <Function test_showhelo[smtp.gmail.com]>
    <Function test_showhelo[mail.python.org]>
  <Module test_emaillib.py>
    <Function test_email_received>
  <Module test_finalizers.py>
    <Function test_bar>
  <Module test_ids.py>
    <Function test_a[spam]>
    <Function test_a[ham]>
    <Function test_b[eggs]>
    <Function test_b[1]>
  <Module test_module.py>
    <Function test_ehlo[smtp.gmail.com]>
    <Function test_noop[smtp.gmail.com]>
    <Function test_ehlo[mail.python.org]>
    <Function test_noop[mail.python.org]>

======================= 12 tests collected in 0.12s ========================

パラメータ化されたフィクスチャでマークを使用する

pytest.param() は、@pytest.mark.parametrize と同じ方法で、パラメータ化されたフィクスチャの値セットにマークを適用するために使用できます。

例:

# content of test_fixture_marks.py
import pytest


@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
    return request.param


def test_data(data_set):
    pass

このテストを実行すると、値 2data_set の呼び出しが スキップ されます:

$ pytest test_fixture_marks.py -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 3 items

test_fixture_marks.py::test_data[0] PASSED                           [ 33%]
test_fixture_marks.py::test_data[1] PASSED                           [ 66%]
test_fixture_marks.py::test_data[2] SKIPPED (unconditional skip)     [100%]

======================= 2 passed, 1 skipped in 0.12s =======================

モジュール性:フィクスチャ関数からフィクスチャを使用する

テスト関数でフィクスチャを使用することに加えて、フィクスチャ関数自体が他のフィクスチャを使用することもできます。 これにより、フィクスチャのモジュール設計に貢献し、フレームワーク固有のフィクスチャを多くのプロジェクトで再利用できるようになります。 簡単な例として、前の例を拡張して、すでに定義されている smtp_connection リソースを挿入するオブジェクト app をインスタンス化できます:

# content of test_appsetup.py

import pytest


class App:
    def __init__(self, smtp_connection):
        self.smtp_connection = smtp_connection


@pytest.fixture(scope="module")
def app(smtp_connection):
    return App(smtp_connection)


def test_smtp_connection_exists(app):
    assert app.smtp_connection

ここでは、以前に定義された smtp_connection フィクスチャを受け取り、それを使用して App オブジェクトをインスタンス化する app フィクスチャを宣言します。 実行してみましょう:

$ pytest -v test_appsetup.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 2 items

test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%]

============================ 2 passed in 0.12s =============================

smtp_connection のパラメータ化により、テストは2つの異なる App インスタンスとそれぞれの smtp サーバーで2回実行されます。 pytest はフィクスチャの依存関係グラフを完全に分析するため、app フィクスチャが smtp_connection のパラメータ化を認識する必要はありません。

app フィクスチャは module のスコープを持ち、モジュールスコープの smtp_connection フィクスチャを使用することに注意してください。 smtp_connectionsession スコープでキャッシュされている場合でも、例は機能します。 フィクスチャが「より広い」スコープのフィクスチャを使用することは問題ありませんが、その逆は意味がありません。 セッションスコープのフィクスチャは、モジュールスコープのフィクスチャを意味のある方法で使用することはできません。

フィクスチャインスタンスによるテストの自動グループ化

pytest はテスト実行中のアクティブなフィクスチャの数を最小限に抑えます。 パラメータ化されたフィクスチャがある場合、それを使用するすべてのテストは最初に1つのインスタンスで実行され、次のフィクスチャインスタンスが作成される前にファイナライザが呼び出されます。 これにより、グローバル状態を作成および使用するアプリケーションのテストが容易になります。

次の例では、2つのパラメータ化されたフィクスチャを使用します。 そのうちの1つはモジュールごとにスコープされており、すべての関数が print 呼び出しを実行してセットアップ/後片付けのフローを示します:

# content of test_module.py
import pytest


@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
    param = request.param
    print("  SETUP modarg", param)
    yield param
    print("  TEARDOWN modarg", param)


@pytest.fixture(scope="function", params=[1, 2])
def otherarg(request):
    param = request.param
    print("  SETUP otherarg", param)
    yield param
    print("  TEARDOWN otherarg", param)


def test_0(otherarg):
    print("  RUN test0 with otherarg", otherarg)


def test_1(modarg):
    print("  RUN test1 with modarg", modarg)


def test_2(otherarg, modarg):
    print(f"  RUN test2 with otherarg {otherarg} and modarg {modarg}")

テストを詳細モードで実行し、print 出力を確認しましょう:

$ pytest -v -s test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 8 items

test_module.py::test_0[1]   SETUP otherarg 1
  RUN test0 with otherarg 1
PASSED  TEARDOWN otherarg 1

test_module.py::test_0[2]   SETUP otherarg 2
  RUN test0 with otherarg 2
PASSED  TEARDOWN otherarg 2

test_module.py::test_1[mod1]   SETUP modarg mod1
  RUN test1 with modarg mod1
PASSED
test_module.py::test_2[mod1-1]   SETUP otherarg 1
  RUN test2 with otherarg 1 and modarg mod1
PASSED  TEARDOWN otherarg 1

test_module.py::test_2[mod1-2]   SETUP otherarg 2
  RUN test2 with otherarg 2 and modarg mod1
PASSED  TEARDOWN otherarg 2

test_module.py::test_1[mod2]   TEARDOWN modarg mod1
  SETUP modarg mod2
  RUN test1 with modarg mod2
PASSED
test_module.py::test_2[mod2-1]   SETUP otherarg 1
  RUN test2 with otherarg 1 and modarg mod2
PASSED  TEARDOWN otherarg 1

test_module.py::test_2[mod2-2]   SETUP otherarg 2
  RUN test2 with otherarg 2 and modarg mod2
PASSED  TEARDOWN otherarg 2
  TEARDOWN modarg mod2


============================ 8 passed in 0.12s =============================

パラメータ化されたモジュールスコープの modarg リソースが、可能な限り少ない「アクティブ」リソースをもたらすテスト実行の順序を引き起こしたことがわかります。 mod1 パラメータ化リソースのファイナライザは、mod2 リソースがセットアップされる前に実行されました。

特に、test_0 が完全に独立しており、最初に終了することに注意してください。 次に、test_1 が mod1 で実行され、次に test_2 が mod1 で実行され、次に test_1 が mod2 で実行され、最後に test_2 が mod2 で実行されます。

otherarg パラメータ化リソース (関数スコープを持つ) は、使用するすべてのテストの前にセットアップされ、後に後片付けされました。

クラスおよびモジュールで usefixtures を使用してフィクスチャを使用する

テスト関数がフィクスチャオブジェクトに直接アクセスする必要がない場合があります。 たとえば、テストでは空のディレクトリを現在の作業ディレクトリとして操作する必要がありますが、具体的なディレクトリには関心がありません。 標準の tempfile および pytest フィクスチャを使用してこれを実現する方法は次のとおりです。 フィクスチャの作成を conftest.py ファイルに分離します:

# content of conftest.py

import os
import tempfile

import pytest


@pytest.fixture
def cleandir():
    with tempfile.TemporaryDirectory() as newpath:
        old_cwd = os.getcwd()
        os.chdir(newpath)
        yield
        os.chdir(old_cwd)

そして、usefixtures マーカーを介してテストモジュールでその使用を宣言します:

# content of test_setenv.py
import os

import pytest


@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
    def test_cwd_starts_empty(self):
        assert os.listdir(os.getcwd()) == []
        with open("myfile", "w", encoding="utf-8") as f:
            f.write("hello")

    def test_cwd_again_starts_empty(self):
        assert os.listdir(os.getcwd()) == []

usefixtures マーカーのため、各テストメソッドの実行には cleandir フィクスチャが必要になります。 これは、各テストメソッドに「cleandir」関数引数を指定した場合と同じです。 フィクスチャがアクティブ化され、テストが合格することを確認するために実行してみましょう:

$ pytest -q
..                                                                   [100%]
2 passed in 0.12s

このように複数のフィクスチャを指定できます:

@pytest.mark.usefixtures("cleandir", "anotherfixture")
def test(): ...

そして、pytestmark を使用してテストモジュールレベルでフィクスチャの使用を指定できます:

pytestmark = pytest.mark.usefixtures("cleandir")

プロジェクト内のすべてのテストに必要なフィクスチャを ini ファイルに入れることも可能です:

# content of pytest.ini
[pytest]
usefixtures = cleandir

警告

このマークは フィクスチャ関数 には影響しません。 たとえば、これは 期待どおりに機能しません

@pytest.mark.usefixtures("my_other_fixture")
@pytest.fixture
def my_fixture_that_sadly_wont_use_my_other_fixture(): ...

これにより非推奨警告が生成され、Pytest 8 ではエラーになります。

さまざまなレベルでフィクスチャをオーバーライドする

比較的大規模なテストスイートでは、テストコードを読みやすく保守しやすくするために、global または root フィクスチャを locally 定義されたものに override する必要がある可能性が高いです。

フォルダ (conftest) レベルでフィクスチャをオーバーライドする

テストファイルの構造が次のようになっているとします:

tests/
    conftest.py
        # content of tests/conftest.py
        import pytest

        @pytest.fixture
        def username():
            return 'username'

    test_something.py
        # content of tests/test_something.py
        def test_username(username):
            assert username == 'username'

    subfolder/
        conftest.py
            # content of tests/subfolder/conftest.py
            import pytest

            @pytest.fixture
            def username(username):
                return 'overridden-' + username

        test_something_else.py
            # content of tests/subfolder/test_something_else.py
            def test_username(username):
                assert username == 'overridden-username'

ご覧のとおり、同じ名前のフィクスチャは特定のテストフォルダレベルでオーバーライドできます。 base または super フィクスチャには overriding フィクスチャから簡単にアクセスできます - 上記の例で使用されています。

テストモジュールレベルでフィクスチャをオーバーライドする

テストファイルの構造が次のようになっているとします:

tests/
    conftest.py
        # content of tests/conftest.py
        import pytest

        @pytest.fixture
        def username():
            return 'username'

    test_something.py
        # content of tests/test_something.py
        import pytest

        @pytest.fixture
        def username(username):
            return 'overridden-' + username

        def test_username(username):
            assert username == 'overridden-username'

    test_something_else.py
        # content of tests/test_something_else.py
        import pytest

        @pytest.fixture
        def username(username):
            return 'overridden-else-' + username

        def test_username(username):
            assert username == 'overridden-else-username'

上記の例では、同じ名前のフィクスチャは特定のテストモジュールでオーバーライドできます。

直接テストパラメータ化でフィクスチャをオーバーライドする

テストファイルの構造が次のようになっているとします:

tests/
    conftest.py
        # content of tests/conftest.py
        import pytest

        @pytest.fixture
        def username():
            return 'username'

        @pytest.fixture
        def other_username(username):
            return 'other-' + username

    test_something.py
        # content of tests/test_something.py
        import pytest

        @pytest.mark.parametrize('username', ['directly-overridden-username'])
        def test_username(username):
            assert username == 'directly-overridden-username'

        @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
        def test_username_other(other_username):
            assert other_username == 'other-directly-overridden-username-other'

上記の例では、フィクスチャ値がテストパラメータ値によってオーバーライドされます。 テストがそれを直接使用しない場合でも (関数プロトタイプで言及していない場合でも) 、フィクスチャの値はこの方法でオーバーライドできることに注意してください。

パラメータ化されたフィクスチャを非パラメータ化されたフィクスチャでオーバーライドし、その逆も可能

テストファイルの構造が次のようになっているとします:

tests/
    conftest.py
        # content of tests/conftest.py
        import pytest

        @pytest.fixture(params=['one', 'two', 'three'])
        def parametrized_username(request):
            return request.param

        @pytest.fixture
        def non_parametrized_username(request):
            return 'username'

    test_something.py
        # content of tests/test_something.py
        import pytest

        @pytest.fixture
        def parametrized_username():
            return 'overridden-username'

        @pytest.fixture(params=['one', 'two', 'three'])
        def non_parametrized_username(request):
            return request.param

        def test_username(parametrized_username):
            assert parametrized_username == 'overridden-username'

        def test_parametrized_username(non_parametrized_username):
            assert non_parametrized_username in ['one', 'two', 'three']

    test_something_else.py
        # content of tests/test_something_else.py
        def test_username(parametrized_username):
            assert parametrized_username in ['one', 'two', 'three']

        def test_username(non_parametrized_username):
            assert non_parametrized_username == 'username'

上記の例では、パラメータ化されたフィクスチャが非パラメータ化されたバージョンでオーバーライドされ、非パラメータ化されたフィクスチャが特定のテストモジュールのパラメータ化されたバージョンでオーバーライドされます。 同じことがテストフォルダレベルにも適用されます。

他のプロジェクトからフィクスチャを使用する

通常、pytest サポートを提供するプロジェクトは entry points を使用するため、それらのプロジェクトを環境にインストールするだけで、それらのフィクスチャが使用可能になります。

エントリーポイントを使用しないプロジェクトからフィクスチャを使用したい場合は、トップ conftest.py ファイルに pytest_plugins を定義して、そのモジュールをプラグインとして登録できます。

mylibrary.fixtures にいくつかのフィクスチャがあり、それらを app/tests ディレクトリで再利用したいとします。

必要なのは、app/tests/conftest.pypytest_plugins を定義して、そのモジュールを指すことだけです。

pytest_plugins = "mylibrary.fixtures"

これにより、mylibrary.fixtures がプラグインとして効果的に登録され、そのすべてのフィクスチャとフックが app/tests のテストで使用可能になります。

注釈

ユーザーが他のプロジェクトからフィクスチャを インポート して使用することがありますが、これは推奨されません。 フィクスチャをモジュールにインポートすると、そのモジュールで 定義 されたものとして pytest に登録されます。

これには、pytest --help に複数回表示されるなどの小さな影響がありますが、この動作は将来のバージョンで変更/動作しなくなる可能性があるため、推奨されません