fixture って何?まずはイメージから
pytest の fixture(フィクスチャ)は、
「テストのたびに必要になる“準備”を、共通化して再利用する仕組み」です。
毎回のテストで同じようなことをしていませんか?
テスト用のデータを用意する
一時ファイルや一時ディレクトリを作る
DB 接続や Session を作る
テスト後に片付ける(クローズ・削除など)
これを全部テスト関数の中に書いていると、
コードが重複するし、片付け忘れもしやすい。
fixture は、こういう「前準備」と「後片付け」を
一箇所にまとめて、テストからは「引数として受け取るだけ」にしてくれる仕組みです。
いちばん小さい fixture の例から始める
テスト対象の関数を用意する
まずは、シンプルな関数を用意します。calc.py とします。
# calc.py
def add(a: int, b: int) -> int:
return a + b
Pythonここに対して、「よく使う入力セット」を fixture で用意してみます。
fixture を定義する基本形
pytest の fixture は、@pytest.fixture を付けた関数です。
# test_calc.py
import pytest
from calc import add
@pytest.fixture
def sample_numbers():
return (1, 2)
def test_add_with_sample_numbers(sample_numbers):
a, b = sample_numbers
assert add(a, b) == 3
Pythonここで起きていることを分解すると、
sample_numbers という名前の fixture を定義している
テスト関数 test_add_with_sample_numbers の引数に sample_numbers と書く
pytest が「この引数名と同じ fixture を探して、戻り値を渡してくれる」
という流れです。
テスト側から見ると、
「引数に書くだけで、準備済みのデータがもらえる」
という感覚で使えます。
fixture の一番おいしいところ:「共通の準備」を一箇所にできる
fixture がないときの重複コード
例えば、ユーザー辞書を使う関数をテストしたいとします。
# user.py
def get_full_name(user: dict) -> str:
return f"{user['last_name']} {user['first_name']}"
Pythonfixture を使わないと、テストはこうなりがちです。
# test_user.py
from user import get_full_name
def test_get_full_name_normal():
user = {"first_name": "Taro", "last_name": "Yamada"}
assert get_full_name(user) == "Yamada Taro"
def test_get_full_name_other():
user = {"first_name": "Hanako", "last_name": "Suzuki"}
assert get_full_name(user) == "Suzuki Hanako"
Pythonここではまだマシですが、
もっと複雑な「ユーザーオブジェクト」を毎回作るようになると、
テスト関数ごとに同じような準備コードが並び始めます。
fixture にまとめるとスッキリする
同じ準備を fixture にすると、こう書けます。
# test_user.py
import pytest
from user import get_full_name
@pytest.fixture
def user_taro():
return {"first_name": "Taro", "last_name": "Yamada"}
@pytest.fixture
def user_hanako():
return {"first_name": "Hanako", "last_name": "Suzuki"}
def test_get_full_name_normal(user_taro):
assert get_full_name(user_taro) == "Yamada Taro"
def test_get_full_name_other(user_hanako):
assert get_full_name(user_hanako) == "Suzuki Hanako"
Pythonテスト関数の中から「準備コード」が消えて、
「何をテストしているか」だけが見えるようになります。
fixture の役割は、
テストの「前準備」を一箇所に集める
テスト本体は「振る舞いの確認」に集中させる
というところにあります。
前準備だけじゃない:「後片付け」も fixture に任せられる
一時ファイルを使うテストを考える
例えば、「ファイルに書き込んでから読む」関数をテストしたいとします。
# fileutil.py
def write_and_read(path: str, text: str) -> str:
with open(path, "w", encoding="utf-8") as f:
f.write(text)
with open(path, "r", encoding="utf-8") as f:
return f.read()
Pythonテストでは、一時ファイルを作って、終わったら消したくなります。
fixture を使わないと、テスト関数の中でこうなります。
# test_fileutil.py
import os
from fileutil import write_and_read
def test_write_and_read_tmpfile():
path = "tmp_test.txt"
try:
result = write_and_read(path, "hello")
assert result == "hello"
finally:
if os.path.exists(path):
os.remove(path)
Pythonテストごとに try/finally と削除処理を書くのは面倒です。
fixture で「前準備+後片付け」をまとめる
pytest の fixture は、yield を使うことで
「前準備」と「後片付け」を 1 つの関数にまとめられます。
# test_fileutil.py
import os
import pytest
from fileutil import write_and_read
@pytest.fixture
def tmp_file_path():
path = "tmp_test.txt"
yield path
if os.path.exists(path):
os.remove(path)
def test_write_and_read_tmpfile(tmp_file_path):
result = write_and_read(tmp_file_path, "hello")
assert result == "hello"
Pythonここでの流れはこうです。
fixture 関数が呼ばれるyield path まで実行され、その値がテスト関数に渡される
テスト関数が実行される
テストが終わったあと、yield の後ろのコードが実行される(後片付け)
つまり、yield を境に、
前半:セットアップ(準備)
後半:ティアダウン(片付け)
を 1 つの fixture にまとめられるわけです。
これが、DB 接続のクローズ、トランザクションのロールバック、
一時ディレクトリの削除などでめちゃくちゃ役に立ちます。
fixture のスコープ(どの単位で使い回すか)
scope=”function”(デフォルト)
何も指定しないと、fixture は「テスト関数ごと」に実行されます。
@pytest.fixture
def sample():
print("setup")
return 123
Pythonこの fixture を 3 つのテストで使うと、
3 回「setup」が実行されます。
テストごとに完全に独立した状態が欲しいときは、
このデフォルトの挙動が安全です。
scope=”module” や “session”
「毎回作り直すのは重いから、モジュール単位・テスト全体で共有したい」
というケースもあります。
例えば、テスト用の DB を 1 回だけ立ち上げたいときなどです。
@pytest.fixture(scope="module")
def db_connection():
print("connect DB")
conn = create_connection()
yield conn
print("close DB")
conn.close()
Pythonscope="module" にすると、
そのファイル(モジュール)のテストが始まるときに 1 回だけ作られる
そのモジュール内のテストから共有される
モジュールのテストが全部終わったら片付けが走る
という動きになります。
scope="session" にすると、「pytest 実行全体で 1 回だけ」になります。
スコープをどうするかは、
毎回クリーンな状態が必要か
重い初期化を何度もやりたくないか
のバランスで決めます。
fixture を使うときに意識してほしい設計の感覚
「テストが何を前提にしているか」を fixture 名で表す
fixture 名は、そのテストが「何を前提にしているか」を表す名前にすると読みやすくなります。
例えば、
user_tarologged_in_clientdb_sessiontmp_dir
のように、「このテストはこういう状態から始まる」というのが
引数名だけで伝わると、テストコードがかなり自己説明的になります。
逆に、data1、obj、env みたいな名前だと、
「これ何だっけ?」と毎回定義を見に行くことになりがちです。
「準備が複雑になってきたら fixture に逃がす」
最初は、テスト関数の中に直接準備コードを書いていても構いません。
ただ、
同じ準備が複数のテストで繰り返されている
テスト関数の上半分が準備で埋まっている
と感じたら、それは fixture に切り出すサインです。
fixture に切り出すと、
準備のロジックを 1 箇所で管理できる
テスト本体は「期待する振る舞い」だけに集中できる
後片付けもまとめて書ける
というメリットが一気に出てきます。
まとめ(fixture は「テストの土台を整える職人」)
pytest の fixture を初心者目線で整理すると、こうなります。
@pytest.fixture を付けた関数で「前準備」を定義し、テスト関数の引数にその名前を書くと、pytest が自動でその戻り値を渡してくれる。yield を使うと、「前準備」と「後片付け」を 1 つの fixture にまとめられる。テストが終わったあとに yield の後ろが実行される。
scope を変えることで、「テストごと」「ファイルごと」「テスト全体」で使い回すかを調整できる。
同じ準備コードがテストに散らばり始めたら、それを fixture に集約することで、テストが読みやすく・壊れにくくなる。
