pytestの組み込みフィクスチャで効果的な前処理・後処理を実装しよう!

eyecatch-pytest-built-in-fixture
目次

概要

フィクスチャと組み込みフィクスチャ

pytestはPythonのテスティングフレームワークの1つであり、Python標準モジュールに組み込まれているunittestと並んでPythonのテスト実装でよく使われています。

テストコードを実装する際、テストケースによっては前処理(setup)や後処理(teardown)をする場合があります。pytestではフィクスチャという機能を用いて処理を実装しますが、その一部の機能として組み込みフィクスチャが用意されています。組み込みフィクスチャを利用することで前処理・後処理の実装にいろいろなメリットがあります。

  1. テストの再利用性が向上
    • 組み込みフィクスチャは、一般的なテストシナリオに必要な処理を提供しているため、同じセットアップを繰り返し記述する必要がなくなります。たとえば、ファイルやディレクトリの操作には tmp_path を、標準入出力のキャプチャには capsys が役立ちます。
  2. テストの可読性と簡潔化
    • 組み込みフィクスチャはよく使われる操作の処理を包括しているため、コードが読みやすくなり、テストの目的が明確になります。たとえば、monkeypatch は一時的に関数やオブジェクトを置き換えることでテスト目的をより理解しやすくします。
  3. 柔軟な前処理と後処理
    • pytest の組み込みフィクスチャは、必要なリソースのセットアップと解放を適切に処理します。たとえば、tmp_path は一時ディレクトリを作成し、テストが終わった後に自動的に削除します。
  4. 高いメンテナンス性
    • 組み込みフィクスチャを使用することで、テスト用コードの変更や修正が容易になります。複雑なテストセットアップが簡略化されるため、テストコードの変更時の修正量も減り、メンテナンスがしやすくなります。

本記事ではフィクスチャについては詳細に解説しませんが、下記の記事で説明をしています。フィクスチャについてご興味のある方や復習したい方はご参考ください。

組み込みフィクスチャの使い方

pytestには組み込みフィクスチャとして決められた予約語があり、それをテストコードの引数に記述することで機能を実現することが可能です。例えば下記の例ではcapsysのコードを示しています。capsysはテスト中の標準出力print()や標準エラー出力をキャプチャし、テスト内で出力内容を確認できます。

# test_capsys_example.py
def test_capsys_example(capsys):
    print("Hello, capsys!")
    captured = capsys.readouterr()  # 標準出力と標準エラー出力を取得
    assert captured.out == "Hello, capsys!\n"

組み込みフィクスチャの一覧

pytestの組み込みフィクスチャを確認したい場合はこちらの公式サイトで確認してみてください。下図のようにさまざまな組み込みフィクスチャが用意されていることがわかります。

pytest-built-in-fixture
docs.pytest.org, https://docs.pytest.org/en/latest/reference/fixtures.html#reference-fixtures

よく使う組み込みフィクスチャの実装

前提

pytestの実行環境構築は下記の記事をご参考ください。

tmp_pathフィクスチャ

この組み込みフィクスチャはテストが実行される間だけ存在する一時ディレクトリを提供します。tmp_pathを使うと、簡単に一時ファイルやディレクトリを作成してテストでき、テスト終了後には自動的に削除されるため、テスト環境を汚さずに済みます。

なお3行目のfileは、Pathオブジェクトです。これはPythonの標準ライブラリのpathlibモジュールから提供されるクラスで、ファイルシステム上のパス(ファイルやディレクトリ)を表します。

def test_tmp_path(tmp_path):
    # 一時ディレクトリ内にファイルを作成
    file = tmp_path / "sample.txt"
    file.write_text("Hello, tmp_path!")  # 指定のパスに新しいファイルを作成しテキストを書き込む

    # ファイルの存在と内容を確認
    assert file.read_text() == "Hello, tmp_path!"
    assert file.exists()

tmp_path_factoryフィクスチャ

この組み込みフィクスチャはtmp_pathと似ていますが、異なるテスト関数間で共有できる一時ディレクトリを作成するために使います。3行目のtmp_path_factory.mktemp()でディレクトリ作成しtmp_path_factoryで作成したディレクトリはテストごとに一度だけ作成できるため、複数のテストで共通の一時ディレクトリを利用できます。

def test_tmp_path_factory(tmp_path_factory):
    # 一時ディレクトリを作成(テスト間で共有可能)
    shared_dir = tmp_path_factory.mktemp("shared")
    file = shared_dir / "shared_sample.txt"
    file.write_text("Hello, tmp_path_factory!")

    # ファイルの存在と内容を確認
    assert file.read_text() == "Hello, tmp_path_factory!"
    assert file.exists()

capsysフィクスチャ

この組み込みフィクスチャはprint()などで標準出力や標準エラーに出力される内容をキャプチャします。これを利用してcapsys.readouterr()を呼び出すとテストの間に発生したすべての標準出力とエラー出力を取得できます。これにより、特定のメッセージが出力されているか確認でき、標準出力が正しいことを確認するテストに便利です。

def test_capsys(capsys):
    print("Hello, capsys!")  # 標準出力
    print("Capturing output is useful")  # 標準出力

    # 出力のキャプチャと確認
    captured = capsys.readouterr()
    assert "Hello, capsys!" in captured.out
    assert "Capturing output is useful" in captured.out

6行目のcapturedにはcapsys.readouterr()によってキャプチャされた標準出力(stdout)の内容や標準エラー(stderr)の内容が格納されています。capturedは以下のような2つの属性を持つオブジェクトです:

captured.err: 標準エラーに出力された文字列(エラーメッセージなど)
captured.out: 標準出力に出力された文字列(print文など)

上記のコードでは、2、3行目の標準出力にprint("Hello, capsys!")print("Capturing output is useful")があるので、captured.outにはこれらの文字列が改行を含んだ形で格納されます(下記コードを参照)。このためassert "Hello, capsys!" in captured.outassert "Capturing output is useful" in captured.outといったテストで、標準出力に期待する文字列が出力されているかを確認できます。

captured.out  # "Hello, capsys!\nCapturing output is useful\n"
captured.err  # "" (このコードでは標準エラーへの出力がないため空文字列)

monkeypatchフィクスチャ

この組み込みフィクスチャは環境変数の変更や既存関数の動作を一時的に上書きするための簡易的なモック機能を提供します。monkeypatch.setenv()を使って環境変数を一時的に設定したり、monkeypatch.setattr()を使って関数をモック化できます。これにより、テスト中にシステム環境や関数の振る舞いを自由に変更できます。

なおpytestで利用できるモックはたくさんあるため別の記事でまとめたいと思います。

import os

def test_monkeypatch(monkeypatch):
    # 環境変数を一時的に設定
    monkeypatch.setenv("MY_ENV_VAR", "test_value")
    assert os.getenv("MY_ENV_VAR") == "test_value"  # 環境変数が設定されているか確認

    # 関数の動作を一時的に変更
    def mock_function():
        return "mocked!"
    
    monkeypatch.setattr("os.getcwd", mock_function)  # os.getcwdをモック化
    assert os.getcwd() == "mocked!"  # モック化された関数の結果を確認

まとめ

本記事では組み込みフィクスチャを解説しました。具体的に紹介した組み込みフィクスチャはよく利用されるものですが、実際にはたくさんの種類があります。まずは本記事で紹介した組み込みフィクスチャを覚えつつ、実際にテストコードを書いているときに適宜調べてみるとよいでしょう。

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

この記事を書いた人

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

目次