pytestのデコレータ実践ガイド(parametrize / fixture / mark / skip / xfail)
🧭 はじめに
このページでは、pytestで頻出する「デコレータ(decorator)」を、実務で迷わず使える粒度で整理します。
特に、
- テストケースが増えて重複がつらい
- 「条件付きでskipしたい」
- 「失敗しても良いテストを管理したい」
- 「テストをカテゴリ分けしたい」
- fixtureをどのスコープで使うべきか覚えてない
みたいな場面で、忘れても復帰できることを狙います。
結論:pytestのデコレータは「冗長なテストを短くする」ための道具で、正しく使うとテストの可読性と保守性が上がります。
🧩 pytestの「デコレータ」とは何か?
pytestでのデコレータは、ざっくり以下の用途に分かれます。
- テストを 増やす(parameterize)
- テストを 分類する(mark)
- テストを 止める / 条件で止める(skip / xfail)
- fixture(前処理)に 設定を与える(fixture decorator)
つまり「テスト関数の見た目を保ちつつ、振る舞いを追加する」手段です。
🧪 @pytest.mark.parametrize(最重要)
✅ 何のために使う?
同じテスト構造で入力だけ変えたいときの王道です。
- if分岐でまとめるのはNGになりがち
- テスト関数をコピーしまくるのもNG
その間を埋める最強の仕組みが parametrize。
✅ 基本形(1引数)
import pytest
@pytest.mark.parametrize('x', [1, 2, 3])
def test_is_positive(x: int) -> None:
assert x > 0
この1個の関数が 3つのテストとして実行されます。
✅ 2引数以上(典型)
import pytest
@pytest.mark.parametrize(
('a', 'b', 'expected'),
[
(1, 2, 3),
(10, 20, 30),
],
)
def test_add(a: int, b: int, expected: int) -> None:
assert a + b == expected
おすすめ:パラメータは(a,b,expected)形式が読みやすく、失敗時も追いやすい。
✅ 異常系(例外)テストを短くする
import pytest
@pytest.mark.parametrize(
('bad_book_id',),
[
(0,),
(-1,),
(None,),
('1',),
],
)
def test_validate_bad_book_id(bad_book_id: object) -> None:
with pytest.raises(ValueError):
# validate_and_normalize(...)
...
こういう「ダメ入力集合」は parametrize に押し込むのが鉄板です。
✅ id=(失敗時に読みやすくする)
parametrizeはデフォルトだと (0,) みたいな表示で分かりにくいことがあります。
idを付けるとログが読みやすくなります。
import pytest
@pytest.mark.parametrize(
'bad_book_id',
[0, -1, None, '1'],
ids=['zero', 'negative', 'none', 'string'],
)
def test_validate_bad_book_id(bad_book_id: object) -> None:
...
idsは「デバッグのため」に付ける。必須ではないが、時間が経つほど効く。
✅ pytest.param()(個別にidやマークを付ける)
import pytest
@pytest.mark.parametrize(
'x',
[
pytest.param(1, id='small'),
pytest.param(1000, id='large'),
],
)
def test_something(x: int) -> None:
...
さらに「このケースだけskip」みたいな運用も可能。
import pytest
@pytest.mark.parametrize(
'x',
[
pytest.param(1, id='ok'),
pytest.param(999, marks=pytest.mark.skip(reason='まだ仕様未確定'), id='pending'),
],
)
def test_case(x: int) -> None:
...
🧰 @pytest.fixture(前処理の王道)
✅ 何のために使う?
テストの前後処理を「見える化」しつつ使い回すためです。
- 毎回同じ準備を書くのはダルい
- でも
setup()みたいな暗黙の仕組みは追いづらい
pytestは fixture を明示的に差し込む設計なので読みやすいです。
✅ 基本形
import pytest
@pytest.fixture
def base_config() -> dict:
return {'book_id': 1, 'chapters': [{'name': '01', 'pages': []}]}
def test_validate_ok(base_config: dict) -> None:
...
base_config引数に書くだけで注入される- 引数名がfixture名
✅ scope= の意味(重要)
fixtureは「どの単位で使い回されるか」を選べます。
function(デフォルト):テスト関数ごとに生成class:クラスごとmodule:ファイルごとsession:pytest全体で1回
import pytest
@pytest.fixture(scope='session')
def expensive_resource():
...
sessionスコープは便利だが注意:テスト間で状態を共有して壊れやすい。基本は function が安全。
✅ fixtureにパラメータを渡したい場合(fixtureのparam化)
import pytest
@pytest.fixture(params=[1, 2, 3])
def n(request) -> int:
return request.param
ただし、読みにくくなりやすいので、単純な場合は parametrize を優先するのが無難。
🏷️ @pytest.mark.*(分類・制御)
✅ markの用途
- テストをカテゴリ分けする
- 実行対象を絞る
- CIやローカルで切り替える
典型:
slow(遅いテスト)integration(統合テスト)network(外部通信あり)
✅ 例:@pytest.mark.slow
import pytest
@pytest.mark.slow
def test_big_data() -> None:
...
実行を絞る:
pytest -m slow
pytest -m "not slow"
ローカルは全部回さない運用にしたいなら、markが最強のスイッチになる。
✅ markを新規に作るときの注意(pytest.ini推奨)
markは自由に作れますが、登録しないと警告が出ることがあります。
pytest.ini
[pytest]
markers =
slow: time-consuming tests
integration: integration tests
network: tests that access network
markを運用するならpytest.iniは早めに用意しておくと安定する。
⏭️ @pytest.mark.skip(無条件スキップ)
✅ 使いどころ
「今は仕様未確定」「一時的に無効化したい」など。
import pytest
@pytest.mark.skip(reason='仕様確定待ち')
def test_pending_feature() -> None:
...
🧠 @pytest.mark.skipif(条件付きスキップ)
✅ 典型例:OSやPythonバージョンで分岐
import sys
import pytest
@pytest.mark.skipif(sys.platform.startswith('win'), reason='Windowsでは再現しない')
def test_linux_only() -> None:
...
Pythonバージョン条件:
import sys
import pytest
@pytest.mark.skipif(sys.version_info < (3, 13), reason='Python 3.13+ 前提')
def test_requires_313() -> None:
...
skipifは「環境差異の吸収」に使う。仕様未確定の逃げ道として多用すると腐りやすい。
🧯 @pytest.mark.xfail(失敗しても良いテスト)
✅ 何のために使う?
「失敗することが分かっているが、残しておきたい」テストを管理するため。
- 既知バグの再現テスト
- 将来直す予定
- ただしCIを赤くしたくない
import pytest
@pytest.mark.xfail(reason='既知バグ: issue #123')
def test_known_bug() -> None:
assert do_something() == 1
✅ strict=True(直ったら失敗扱い)
xfail は「失敗するはず」と言ってるので、直って通った場合に気づきたいことがあります。
その場合は strict。
import pytest
@pytest.mark.xfail(strict=True, reason='既知バグ: fixされたらxfailを消す')
def test_known_bug() -> None:
...
strict=True は有能:直ったのにxfailが残ってる状態を検知できる。
🧱 @pytest.mark.usefixtures(引数に書かずfixture適用)
✅ 使いどころ
fixtureは普通「引数に書く」のが分かりやすいですが、 テスト関数の引数を増やしたくない場合に使えます。
import pytest
@pytest.mark.usefixtures('setup_db')
def test_something() -> None:
...
ただし、これはやりすぎると「何が注入されてるか見えない」ので乱用しない。
usefixtures多用は読みづらくなる。基本は引数で明示が正義。
🧬 デコレータを組み合わせるときの型
✅ parametrize + mark
import pytest
@pytest.mark.slow
@pytest.mark.parametrize('x', [1, 2, 3])
def test_heavy(x: int) -> None:
...
または、paramごとにmark:
import pytest
@pytest.mark.parametrize(
'x',
[
pytest.param(1, id='fast'),
pytest.param(2, marks=pytest.mark.slow, id='slow_case'),
],
)
def test_mixed(x: int) -> None:
...
🧭 実務でのおすすめ優先順位
迷ったらこの優先順位が安全。
@pytest.mark.parametrize(重複削減)@pytest.fixture(前処理共通化)@pytest.mark.skipif(環境差異吸収)@pytest.mark.slow等(分類)@pytest.mark.xfail(strict=True)(既知バグ管理)
よくある事故:xfail/skipを増やしすぎて「テストが通っているのか分からない」状態になる。最小限にする。
✅ まとめ
- pytestのデコレータは「テストの重複を減らし、管理しやすくする」道具
parametrizeは最重要(異常系入力集合もここに押し込む)fixtureは前処理の見える化・共通化の基本markは分類・選別に使う(pytest -mと相性が良い)skipifは環境差異の吸収xfail(strict=True)は既知バグの管理に強い
このセットを覚えておけば、pytestの運用が途切れても復帰しやすい。