pytest fixture は、テストコードの重複を減らし、セットアップと後片付けを一元管理できる pytest の中核機能です。yield・scope・conftest.py を正しく使いこなすと、保守性の高いテストが書けるようになります。
📌 この記事の対象読者
| ✅ pytest を使い始めたばかりの方 |
| ✅ fixture の書き方に自信がない方 |
| ✅ yield・scope・conftest.py の違いを整理したい方 |
📖 この記事を読むとわかること
| ✔ fixture の基本構文と動作のしくみ |
| ✔ yield による後片付けパターン(実務で最もよく使う) |
| ✔ scope の選び方と並列実行時の注意点 |
| ✔ conftest.py の適切な置き場所 |
| ✔ autouse・組み込み fixture(tmp_path 等)の活用 |
| ✔ 実務でよくはまるポイントと対処法 |
筆者はQAエンジニアとして15年以上、Selenium・Playwright・pytest を用いたテスト自動化に携わってきました。
本記事では教科書的な説明にとどまらず、実際の現場でよく見るコードの問題点を交えながら解説します。
✅ この記事の結論
- fixture は
yieldを使って「セットアップ+後片付け」を1つにまとめる scopeでコストの高い処理を再利用し、テスト速度を上げる- 共通 fixture は
conftest.pyに置いてチーム全体で使い回す
pytest fixture とは
pytest fixture(フィクスチャ)とは、テスト実行前後に必要なセットアップ・後片付け・共有データをまとめて管理するしくみです。
@pytest.fixture デコレーターをつけた関数を定義し、テスト関数の引数に同名で受け取ると、pytest が自動で注入してくれます。
JUnit でいう @BeforeEach / @AfterEach に近い概念ですが、pytest fixture はスコープの柔軟な制御・ネスト・パラメータ化が可能で、より強力です。
基本構文とシンプルな使い方
まずは最小構成から見てみましょう。
import pytest
# ① fixture を定義する
@pytest.fixture
def sample_data() -> dict:
return {"user": "taro", "age": 30}
# ② テスト関数の引数名 = fixture 名(自動注入)
def test_user_name(sample_data: dict) -> None:
assert sample_data["user"] == "taro"
def test_user_age(sample_data: dict) -> None:
assert sample_data["age"] == 30
sample_data という引数名と fixture 名が一致するだけで、pytest が自動で渡してくれます。
import は不要で、複数のテスト関数から同じ fixture を使い回せるのがポイントです。
おすすめの書き方 5選
1. yield でセットアップ&後片付けを一体化する
実務で最もよく使うパターンです。yield の前がセットアップ、後ろが後片付けになります。
テストが成功しても失敗しても後片付けは必ず実行されます。
import pytest
import requests
import logging
logger = logging.getLogger(__name__)
@pytest.fixture
def api_session() -> requests.Session:
# --- セットアップ ---
session = requests.Session()
session.headers.update({"Content-Type": "application/json"})
logger.info("Session opened")
yield session # ← テスト関数に渡す
# --- 後片付け(テスト成否にかかわらず実行)---
session.close()
logger.info("Session closed")
def test_get_users(api_session: requests.Session) -> None:
response = api_session.get("https://example.com/api/users") # サンプルURL
assert response.status_code == 200
return でも後片付けを実装する方法はありますが、pytest では yield を使う方が teardown を自然に書けます。
DBコネクション・HTTPセッション・ブラウザドライバなど、クローズが必要なリソースには実務では yield パターンが広く使われています。
2. scope でコストの高い処理を使い回す
デフォルトではテストごとに fixture が生成・破棄されます。scope を指定すると生存期間を広げて処理を再利用できます。
| scope | 生存期間 | 主な用途 |
|---|---|---|
function(デフォルト) | テスト1件ごと | 軽い初期化・独立性が必要なデータ |
class | クラス内 | クラス単位の共通データ |
module | ファイル内 | DBコネクション・重いモックサーバー |
session | テスト全体 | ブラウザ起動・認証トークン取得 |
import pytest
import requests
# ログイン API 呼び出しはテスト実行全体で 1 回だけにする
@pytest.fixture(scope="session")
def auth_token() -> str:
response = requests.post(
"https://example.com/api/auth/login", # サンプルURL
json={"username": "testuser", "password": "secret"},
timeout=(3, 10),
)
response.raise_for_status()
data = response.json()
assert "token" in data
return data["token"]
def test_get_profile(auth_token: str) -> None:
headers = {"Authorization": f"Bearer {auth_token}"}
res = requests.get(
"https://example.com/api/profile", # サンプルURL
headers=headers,
timeout=(3, 10),
)
assert res.status_code == 200
scope="session" 自体は問題ありませんが、複数テストで共有される fixture の中で状態を書き換える処理を行うと、pytest-xdist などで並列実行した場合にテスト間で干渉が起きる場合があります。セッションスコープの fixture は読み取り専用のデータ・接続オブジェクトに留めるのが安全です。
3. conftest.py で fixture を共有する
conftest.py に書いた fixture は、同ディレクトリ以下のすべてのテストで import なしに使えます。
チーム全体で使う共通 fixture の置き場所として活用されています。
# ディレクトリ構成
tests/
├── conftest.py ← 全テストで使える共通 fixture
├── test_auth.py
└── api/
├── conftest.py ← api/ ディレクトリ内だけで使える fixture
└── test_users.py
# tests/conftest.py
import pytest
@pytest.fixture(scope="session")
def base_url() -> str:
return "https://example.com" # サンプルURL(実際は環境変数などで管理)
@pytest.fixture
def headers(auth_token: str) -> dict:
return {"Authorization": f"Bearer {auth_token}"}
# tests/test_auth.py
# import は不要! conftest.py の fixture を自動で認識する
def test_base_url(base_url: str) -> None:
assert "example" in base_url
conftest.py は複数階層に置けるので、「プロジェクト全体の共通 fixture」と「特定モジュール専用の fixture」をきれいに分離できます。
4. params でデータ駆動テストを実現する
params を使うと、fixture が返す値を複数パターンに切り替えてテストを繰り返し実行できます。
import pytest
import requests
INVALID_PAYLOADS = [
pytest.param({"username": "", "password": "pass"}, id="empty_username"),
pytest.param({"username": "user", "password": ""}, id="empty_password"),
pytest.param({}, id="empty_body"),
]
# request は pytest が自動で渡す特殊な引数(型ヒントは pytest.FixtureRequest)
@pytest.fixture(params=INVALID_PAYLOADS)
def invalid_login_payload(request) -> dict:
return request.param
def test_login_validation(invalid_login_payload: dict) -> None:
response = requests.post(
"https://example.com/api/auth/login", # サンプルURL
json=invalid_login_payload,
timeout=(3, 10),
)
# 不正なリクエストはすべて 400 系を返すべき
assert response.status_code in (400, 422)
pytest.param(..., id="...") で各パターンに名前を付けると、テスト失敗時のログが読みやすくなります。
fixture の params と @pytest.mark.parametrize の使い分け
| 比較項目 | fixture params | @pytest.mark.parametrize |
|---|---|---|
| 複数テスト関数に同じパターンを適用 | ◎ | △(各関数に個別記載が必要) |
| 1つのテスト関数への単純な入力パターン | △ | ◎ |
| セットアップ処理もパラメータ化したい | ◎ | △ |
| 初心者への読みやすさ | △ | ◎ |
5. fixture を fixture に注入して責務を分ける
fixture の引数に別の fixture を受け取ることができます。「接続情報」「認証」「クライアント生成」をそれぞれ別 fixture に分けることで、責務が明確になります。
fixture をネストする場合は、スコープの広い fixture をスコープの狭い fixture に注入する方向で設計してください。逆方向は動作しません。
session → function ✅(広い→狭い:OK)function → session ❌(狭い→広い:ScopeMismatch エラー)import pytest
import requests
# 1. ベース URL(session スコープ)
@pytest.fixture(scope="session")
def base_url() -> str:
return "https://example.com/api" # サンプルURL(実際は環境変数などで管理)
# 2. 認証トークン(session スコープ・base_url を注入)
@pytest.fixture(scope="session")
def auth_token(base_url: str) -> str:
response = requests.post(
f"{base_url}/auth/login",
json={"username": "testuser", "password": "secret"},
timeout=(3, 10),
)
response.raise_for_status()
data = response.json()
assert "token" in data
return data["token"]
# 3. 認証済みセッション(function スコープ・auth_token を注入)
@pytest.fixture
def authed_session(base_url: str, auth_token: str) -> requests.Session:
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {auth_token}",
"Content-Type": "application/json",
})
yield session
session.close()
# テストは authed_session だけ受け取ればよい
def test_get_users(authed_session: requests.Session) -> None:
res = authed_session.get(
"https://example.com/api/users", # サンプルURL
timeout=(3, 10),
)
assert res.status_code == 200
この構成にすると、base_url を変えるだけで環境切り替えが完結します。
テスト関数側は authed_session しか意識しなくてよいので、可読性も上がります。
6. autouse で全テストに自動適用する(応用)
autouse=True を指定すると、引数に書かなくてもすべてのテストに fixture が自動で適用されます。
ログ出力・タイムゾーン固定・テスト間のDB初期化などに使われることがあります。
import pytest
import logging
logger = logging.getLogger(__name__)
# 全テストの開始・終了をログに記録する
@pytest.fixture(autouse=True)
def log_test_name(request: pytest.FixtureRequest) -> None:
logger.info(f"▶ START: {request.node.name}")
yield
logger.info(f"■ END : {request.node.name}")
autouse=True は便利ですが、使いすぎるとテストがどの fixture に依存しているか分かりにくくなります。「全テストで確実に必要なもの」に絞って使うのが実務では無難です。知っておくと便利な組み込み fixture
pytest にはあらかじめ用意された組み込み fixture があります。自分で実装しなくても使えるので覚えておくと便利です。
| fixture 名 | 用途 | 代表的な使い方 |
|---|---|---|
tmp_path | テスト用の一時ディレクトリ | ファイル読み書きのテスト |
monkeypatch | 環境変数・属性の一時差し替え | 外部 API や設定値のモック |
caplog | ログ出力のキャプチャ | logging の出力内容を assert で検証 |
capsys | 標準出力・標準エラーのキャプチャ | print() の出力を検証したいとき |
from pathlib import Path
# tmp_path:テストごとに自動で一時ディレクトリが生成される
def test_write_csv(tmp_path: Path) -> None:
output_file = tmp_path / "result.csv"
output_file.write_text("id,name\n1,taro\n")
assert output_file.read_text().startswith("id,name")
# monkeypatch:環境変数を一時的に書き換えてテスト後に元に戻す
def test_api_base_url(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("API_BASE_URL", "https://staging.example.com")
import os
assert os.environ["API_BASE_URL"] == "https://staging.example.com"
実務でよくはまるポイント 7選
⚠️ pytest fixture ではまりやすいポイント
- テスト関数名に
test_プレフィックスがない
pytest はデフォルトでtest_で始まる関数のみを収集します。check_login()のように書いても実行されません。 - conftest.py の置き場所が間違っている
conftest.py はテストファイルと同じか上位ディレクトリに置く必要があります。別ディレクトリに置くと「fixture not found」エラーになります。 - scope=”session” の fixture で状態を書き換えている
セッションスコープで共有される fixture の中で状態を変更すると、pytest-xdistなどで並列実行した場合にテスト間の干渉が起きる場合があります。読み取り専用のデータや接続オブジェクトに留めましょう。 - カスタムマークを pytest.ini(または pyproject.toml)に登録していない
@pytest.mark.smokeなどを使う場合、設定ファイルへの登録なしではPytestUnknownMarkWarningが出ます。 - print() の出力が見えない
pytest はデフォルトで標準出力をキャプチャします。確認したい場合はpytest -sを付けるか、loggingモジュールを使いましょう。 - assert 前に例外が出ると FAILED ではなく ERROR になる
fixture や assert より前でエラーが発生すると、テスト結果が FAILED ではなく ERROR と表示されます。メッセージをよく読んでトレースバックを確認しましょう。 - ルートディレクトリ以外から pytest を実行して ModuleNotFoundError が出る
tests/ディレクトリの中からpytestを実行すると、プロジェクトルートが Python パスに含まれず import エラーになることがあります。pytest.iniを置いたルートから実行する習慣をつけましょう。
よくある質問(FAQ)
setup_method / setup_function の違いは? setup_method(クラス内)や setup_function(モジュール内)は xUnit スタイルの初期化メソッドです。pytest fixture は引数注入・スコープ制御・パラメータ化ができる点で機能が豊富です。新しくプロジェクトを始める場合は fixture スタイルが広く使われています。ただし既存の xUnit スタイルのコードをすぐに書き換える必要はなく、混在させることも可能です。
pytest はテスト関数の引数に書かれた fixture の依存関係グラフを解析し、依存される側から順に実行します。同スコープで依存関係がない fixture 同士の順序は保証されていません。実行順序を明示したい場合は fixture を別の fixture に注入して依存関係を作るか、pytest-ordering などのプラグインを検討してください。
autouse=True は使うべきですか?「全テストで必ずセットアップしたい処理」(ログ記録・DB リセット・タイムゾーン固定など)には有効です。一方で乱用するとテストがどの fixture に依存しているかが見えにくくなり、デバッグが難しくなる場合があります。原則としてテスト関数の引数に明示的に書くスタイルを基本とし、autouse はチームで共通認識が取れているケースに限定するのが安全です。
@pytest.mark.parametrize はどう使い分けますか? @pytest.mark.parametrize は1つのテスト関数に複数パターンを適用するシンプルなケースに向いています。fixture の params は、複数のテスト関数に同じパターンを使い回したい場合や、セットアップ処理もあわせてパラメータ化したい場合に便利です。上の比較表も参考にしてください。
yield の後の後片付けコードはテスト失敗時にも実行されますか? 実行されます。yield を使った fixture は try/finally と同等の動作をするため、テストが FAILED になっても後片付けコードは確実に実行されます。ただし後片付けコード自体で例外が発生すると警告が出るので、必要に応じて try/except を入れましょう。
個数に制限はなく、ディレクトリごとに置けます。「プロジェクト共通」「モジュール共通」「テスト種別共通」のようにスコープを意識して分けると、fixture の見通しがよくなります。
まとめ
| ポイント | 内容 |
|---|---|
yield で後片付け | セットアップと teardown を1つの fixture に書く。実務では return より広く使われている |
scope で再利用 | 重い処理は session/module に広げてテストを高速化。状態の書き換えには注意 |
conftest.py で共有 | import なしで fixture を使い回せる。置き場所(ディレクトリ)に注意 |
params でデータ駆動 | 複数テスト関数に同じパターンを適用したいときに便利 |
| fixture の注入 | 責務を分けてネストさせると可読性・保守性が上がる |
autouse=True | 全テスト自動適用。便利だが乱用するとデバッグが難しくなる |
| 組み込み fixture | tmp_path / monkeypatch / caplog など自作不要の便利機能 |
pytest fixture を活用すると、テストコードの重複が減り・後片付け漏れがなくなり・テスト全体の速度も上がります。
最初は yield パターンと conftest.py の使い方だけ押さえておけば、日常的なテスト自動化には十分対応できます。
まずは既存テストのセットアップ処理を1つ fixture に切り出すところから試してみてください。

