Pytest フィクスチャ

完了済み

フィクスチャは、テスト用に定義された信頼性の高いコンテキストを提供します。 カスタムフィクスチャは、@pytest.fixtureで装飾されたPython関数です。 テスト データ、環境変数、データベース接続、テストを実行する前に必要なファイルなどの前提条件を配置するために使用します。 フィクスチャは、テスト スコープまたはフィクスチャ スコープが完了した後にリソースをクリーンアップする破棄手順を定義することもできます。

テストでは、フィクスチャと同じ名前の引数を宣言してフィクスチャを要求します。 Pytest はフィクスチャを検索して実行し、返された値または生成された値をテストに渡します。 フィクスチャのライフサイクルの詳細については、 フィクスチャの pytest ドキュメントを参照してください。

フィクスチャは、セットアップまたはクリーンアップを再利用する場合や、状況が複雑な場合、名前を付けることでより明確になる場合に最も役立ちます。 単純な 1 回限りの値の場合、セットアップ コードをテストに直接保持すると、読みやすくなります。

pytest フィクスチャの主な特性は次のとおりです。

  • スコープコントロール:フィクスチャは scope パラメータを使用して、pytestがフィクスチャ値を作成および破棄する頻度を制御できます。 より広い範囲でパフォーマンスを向上させることができますが、長い間状態を共有することもできます。
  • セットアップと破棄: フィクスチャはクリーンアップに yield を使用できます。 高度なフィクスチャは、組み込みの request フィクスチャ (このユニットの後半で導入) を受け取って、要求するテストを検査し、 request.addfinalizer() を呼び出してクリーンアップ コールバックを登録することもできます。
  • 依存関係の挿入: テストとフィクスチャは引数としてフィクスチャを要求し、依存関係を明示的にします。
  • 再利用性とモジュール性: フィクスチャを 1 回定義し、テスト関数、モジュール、ディレクトリ、またはプロジェクト間で再利用できます。
  • 自動アクティブ化: @pytest.fixture(autouse=True) で定義されたフィクスチャは、引数としてリストされずに、その可用性スコープ内のすべてのテストに対して実行されます。 テストの依存関係が非表示になるため、このオプションは控えめに使用してください。

一時ファイルのフィクスチャの作成

ファイルと対話するテストを記述する場合、他のテストと競合しない、またはハードコーディングされた場所に依存しない分離されたパスが必要なのが一般的です。 Pytest は、一意のディレクトリを作成し、保持を管理するため、通常は新しいテストに最適な組み込みの一時ディレクトリフィクスチャを提供します。 ただし、小さなカスタム フィクスチャを作成することは、フィクスチャ要求と戻り値を理解するのに便利な方法です。

次のフィクスチャは factory 関数を返します。 フィクスチャ自体は、セットアップ中にファイルを作成しません。 代わりに、入れ子になった create() 関数は、テストが tmp_file()を呼び出したときにファイルを作成します。

from pathlib import Path
import tempfile

import pytest


@pytest.fixture
def tmp_file():
    def create(contents=""):
        temp = tempfile.NamedTemporaryFile(delete=False)
        try:
            path = Path(temp.name)
        finally:
            temp.close()

        path.write_text(contents, encoding="utf-8")
        return path

    return create

フィクスチャでは、Pythonの tempfile.NamedTemporaryFile() 関数と delete=False を使用して、閉じた後もファイルが存在します。 ファイル ハンドルは、テストがパスを受け取る前に閉じられます。これは、テストでファイルを再度開くか削除する必要がある場合に重要です (特にWindows)。

テストでフィクスチャを使用する方法の例を次に示します。

def test_file(tmp_file):
    path = tmp_file("ready")
    assert path.read_text(encoding="utf-8") == "ready"

この設定では、 tmp_file はフィクスチャ名であり、テストはフィクスチャが返す create() 関数を受け取ります。 テスト内で tmp_file() を呼び出すと、ファイルが作成され、要求された内容が書き込まれた後、 pathlib.Path オブジェクトが返されます。

Note

フィクスチャをコンテキスト マネージャーと組み合わせる際は、注意してください。 フィクスチャが with ブロック内からパスまたはハンドルを返す場合、コンテキスト マネージャーは、テストでリソースを使用する前にリソースを閉じたり削除したりする可能性があります。 テストでマネージド リソースが必要な場合は、コンテキスト マネージャーを yield 全体で開いたままにするか、リソースを意図的に閉じてから、後で戻ってクリーンアップします。

スコープ管理

既定では、pytest フィクスチャはスコープ function 使用します。 関数スコープのフィクスチャは、テストが要求したときに作成され、そのテストの最後に破棄されます。 フィクスチャでティアダウン コードが使用されている場合、pytest はフィクスチャのスコープの末尾でそのティアダウン コードを実行します。

Pytest では、より広範なフィクスチャ スコープを使用して、高価なセットアップ作業を最適化することもできます。 組み込みのフィクスチャ スコープ名は次のとおりです。

  • function: 既定のスコープ。 フィクスチャはテスト関数ごとに 1 回作成されます。
  • class: フィクスチャはテスト クラスごとに 1 回作成されます。
  • module: フィクスチャはモジュールに対して一度だけ作成されます。
  • package: フィクスチャは、定義されているパッケージに対して 1 回作成され、サブパッケージとサブディレクトリ内のテストを含め、そのパッケージの最後のテストの後に破損します。
  • session: フィクスチャは、テスト セッション用に 1 回作成されます。

高度なケースでは、 scope は、これらのスコープ名のいずれかを返す呼び出し可能な名前にすることもできます。 Pytest は、フィクスチャを定義し、キーワード引数を fixture_name して config渡すときに、関数を 1 回呼び出します。 詳細については、 動的スコープ に関する pytest ドキュメントを参照してください。

このコンテキストでは、 1 回作成 すると、pytest がそのスコープのフィクスチャ値をキャッシュすることを意味します。 たとえば、モジュール スコープフィクスチャは、1 つのモジュール内の複数のテストによって要求できますが、それらのテストは同じキャッシュされたフィクスチャ値を受け取ります。 パラメーター化されたフィクスチャは例外です。pytest は、スコープ内で設定された各パラメーターに新しい値を作成できます。

共有セットアップが安全かつ有用でない限り、フィクスチャは関数単位のスコープで維持します。 変更可能なオブジェクト、開いている接続、環境の変更、ファイルは、より広範なスコープでキャッシュされると、テスト間の状態がリークする可能性があります。

モジュール スコープを使用した tmp_file フィクスチャの外観を次に示します。

from pathlib import Path
import tempfile

import pytest


@pytest.fixture(scope="module")
def tmp_file():
    def create(contents=""):
        temp = tempfile.NamedTemporaryFile(delete=False)
        try:
            path = Path(temp.name)
        finally:
            temp.close()

        path.write_text(contents, encoding="utf-8")
        return path

    return create

このフィクスチャはファクトリ関数を返すので、 scope="module" はモジュールのファクトリ関数をキャッシュします。 テストが tmp_file()を呼び出すたびに、ファクトリは引き続き新しいファイルを作成します。 フィクスチャがファイルパスを直接作成して返した場合、モジュール内のすべてのテストは同じパスを受け取ります。

クリーンアップ管理

以前のバージョンの tmp_file フィクスチャでは一時ファイルが作成されますが、それらのファイルは削除されません。 Pytest では、簡単にクリーンアップを行うために yield フィクスチャをお勧めします。 yield前のコードは、セットアップを実行し、テストに値を提供します。 yield 後のコードは、セットアップの完了後にテストが失敗した場合でも、破棄中に実行されます。 yield に達する前に yield フィクスチャが発生した場合、pytest はその yield 後に破棄コードを実行しませんが、同じテストで正常に完了した以前のフィクスチャは引き続き破棄されます。

次のバージョンでは、ファクトリによって作成されたすべてのファイルが追跡され、テストの完了後にそれらのファイルの削除が試行されます。

from pathlib import Path
import tempfile

import pytest


@pytest.fixture
def tmp_file():
    paths = []

    def create(contents=""):
        temp = tempfile.NamedTemporaryFile(delete=False)
        try:
            path = Path(temp.name)
        finally:
            temp.close()

        paths.append(path)
        path.write_text(contents, encoding="utf-8")
        return path

    yield create

    for path in paths:
        try:
            path.unlink()
        except FileNotFoundError:
            pass

既定の function スコープでは、pytest は、 tmp_fileを要求する各テストの後にクリーンアップ コードを実行します。 フィクスチャを scope="module"に変更した場合、pytest はそのモジュールでテストの実行を完了した後、モジュールの破棄中にクリーンアップを実行します。 複数の yield フィクスチャがアクティブな場合、pytest はセットアップの逆の順序で破棄コードを実行します。

テストによってファイル自体が削除される可能性があるため、クリーンアップでは try/except FileNotFoundError が使用されます。 Windowsでは、テスト中にファイルを開いたままにするとPermissionErrorで削除が失敗する可能性があるため、テストでは作成したハンドルを確実に閉じてください。 さらに制御が必要な場合は、クリーンアップに request.addfinalizer() を使用することもできます。 リソースが作成された後にのみファイナライザーを登録してください。pytest は、後でフィクスチャが失敗してもテスト終了時に追加されたファイナライザーを実行します。 クリーンアップしやすいよう、yield フィクスチャは通常、読みやすくなっています。

conftest.py の使用

各テスト ファイルでフィクスチャを定義する代わりに、共有フィクスチャを conftest.py という名前のファイルに保存できます。 conftest.pyのフィクスチャは、明示的なインポートなしで、同じディレクトリとそのサブディレクトリ内のテストで自動的に使用できます。

テスト スイートには、複数の conftest.py ファイルを含めることができます。 Pytest は、テストが要求するフィクスチャの可用性を判断する際に、テストクラスまたはモジュール内のフィクスチャをそのローカルスコープで優先的に考慮し、次に同じディレクトリおよび親ディレクトリ内の conftest.py ファイルからのフィクスチャを考慮します。 テストでは、フィクスチャを上方向に検索できますが、兄弟ディレクトリや子ディレクトリを下方向に検索することはありません。 プラグインフィクスチャは、ローカルフィクスチャの後で検索されます。 テスト ファイル内の conftest.py からインポートしないでください。pytest で検出できるようにします。

組み込み固定具の探索

Pytest には、テストを効率化するように設計された多くの組み込みフィクスチャがあります。 これらのフィクスチャは、テスト アサーションの記述に集中できるように、一般的なセットアップタスクとクリーンアップ タスクを処理します。 完全な一覧を確認するには、 組み込みのフィクスチャの pytest リファレンスを参照してください。

次のような主要な内蔵設備が含まれています:

  • cache: pytest の実行全体で値を格納および取得します。
  • capsys: stdout および stderrに書き込まれたテキストをキャプチャします。
  • tmp_path: パラメーター化された各ケースを含め、各テスト関数の呼び出しに固有の一時ディレクトリの pathlib.Path オブジェクトを提供します。
  • tmpdir: 一時ディレクトリをレガシ py.path.local オブジェクトとして提供します。 新しいテストの場合、pytest では tmp_pathをお勧めします。
  • monkeypatch: クラス、関数、ディクショナリ、環境変数、およびその他のオブジェクトを一時的に変更します。
  • request: 現在実行中のテストまたはフィクスチャに関する情報を提供し、ファイナライザーをサポートします。

その他の一般的に使用される組み込みフィクスチャには、 caplog (ログ レコードのキャプチャ)、 capfd (ファイル記述子レベルの出力のキャプチャ)、 tmp_path_factory (セッション スコープの一時ディレクトリの作成)、 pytestconfig (アクティブな pytest 構成を公開) などがあります。 完全な一覧については、 組み込みのフィクスチャの pytest リファレンスを参照してください。

次の演習では、 tmp_path と pathlib.Path を使用して、テストごとのディレクトリに一時ファイルを作成します。 既存の pytest プロジェクトで tmpdir が発生する可能性がありますが、新しいコードでは tmp_pathを優先します。 既定では、pytest は最後のいくつかのテスト実行から一時ディレクトリを保持するため、実行後に最新の pytest で管理された一時フォルダーが表示されます。 詳細については、 一時ディレクトリとファイルの pytest ドキュメントを参照してください。

テストにおけるモンキーパッチの役割

前のフィクスチャは、テスト リソースを作成し、クリーンアップを行いました。 フィクスチャのもう 1 つの一般的な用途は、コードを外部状態から分離する必要があるテストでの 一時的 な状態変更です。 そのような場合、pytest は組み込みの monkeypatch フィクスチャを提供します。これはそれ自体が pytest フィクスチャであり、自動破棄の良い例です。

外部リソース、グローバル構成、またはオペレーティング システムの状態に依存するコードのテストは困難な場合があります。 monkeypatchフィクスチャを使用すると、テストの属性、ディクショナリ値、環境変数、現在の作業ディレクトリ、またはsys.pathを一時的に変更できます。 Pytest は、要求するテスト関数またはフィクスチャが完了した後、それらの変更を自動的に元に戻します。

1 つのテストの小さなプロジェクト モジュールの関数属性を置き換える例を次に示します。 (pytest 自体で使用される標準ライブラリ関数ではなく) 範囲が狭いアプリケーション コードにパッチを適用すると、テストに集中し、pytest の動作が誤って中断されるのを回避できます。

# project_module.py
def get_user_id():
    # Imagine this calls a remote service.
    return "real-user"
# test_project.py
import project_module


def test_user_id(monkeypatch):
    monkeypatch.setattr(project_module, "get_user_id", lambda: "test-user")
    assert project_module.get_user_id() == "test-user"

ターゲットを点線のインポート パス文字列として指定することもできます。 Pytest は、パッチを適用するときに名前付きモジュールを解決してインポートします。これは、テスト ファイルにモジュールがまだ必要ない場合に便利です。 この例の後の import project_module は、テストでアサーションの修正プログラムが適用された関数を参照できるようにするためです。

# test_project.py
def test_user_id_by_path(monkeypatch):
    monkeypatch.setattr("project_module.get_user_id", lambda: "test-user")

    import project_module
    assert project_module.get_user_id() == "test-user"

setattr()を使用して属性を設定したりメソッドをオーバーライドしたりする以外に、monkeypatchフィクスチャは、delattr()を使用して属性を削除したり、環境変数 (setenv、delenv)、ディクショナリ項目 (setitem、delitem) を設定および削除したり、sys.path (syspath_prepend)、現在の作業ディレクトリ (chdir) を変更したり、context()で入れ子になった修正スコープを作成したりできます。 raising=Trueとsetattr()の既定のdelattr()動作は、スペルミスのあるターゲットをキャッチするのに役立ちます。 テストまたはフィクスチャの破棄の残りの部分が続行される前にパッチを元に戻す必要がある場合は、monkeypatch.context() を使用します。 詳細については、 モンキーパッチの pytest ドキュメントを参照してください。

monkeypatchは強力ですが、慎重に使用してください。

  • コードの明確さとメンテナンス: monkeypatch を過剰に使用したり、複雑な方法で使用したりすると、テストの理解と保守が困難になる可能性があります。
  • テストの有効性: Monkeypatching では、運用動作とは大きく異なる人工条件下で合格するテストを作成できます。
  • 実装の詳細への過度の依存: 内部実装の詳細にパッチを適用するテストは、基になるコードが変更されたときに脆弱になる可能性があります。
  • デバッグの複雑さ: パッチによってアプリケーションの基本的な動作が変更されると、 monkeypatch を使用するテストのデバッグが困難になる可能性があります。

monkeypatchは、焦点を絞った動作を分離し、各パッチをできるだけ小さく明示的に保つのに役立つ場合に使用します。