pytestの使い方メモ:fixture・parametrize・例外テスト・coverage【2026年版】

Web APIのエンドポイントを作ったときにpytestを使うことが増えたので、実際によく使うものだけ自分用に整理しておく。以前のメモにはコマンドのタイプミスやfixture scopeの考え方で誤解しやすい部分があったので、2026年9月時点で書き直した。

インストール

python -m pip install pytest

coverageも見る場合はpytest-covも入れる。

python -m pip install pytest pytest-cov

まず使うコマンド

プロジェクトルートから実行する。

# 全テスト
pytest

# 詳細表示
pytest -v

# ファイル指定
pytest tests/test_routes.py

# 関数指定
pytest tests/test_routes.py::test_login

# クラス内のテスト指定
pytest tests/test_routes.py::TestAuth::test_login

# 名前で絞り込み
pytest -k "login"

# 最初の失敗で止める
pytest -x

# 2件失敗したら止める
pytest --maxfail=2

# print()をそのまま表示
pytest -s

以前のメモにあったputestは単純なタイプミス。正しくはpytest。

テストファイルの命名

pytestは標準ではtest_*.pyまたは*_test.pyのファイルを収集する。テスト関数はtest_で始める。

# tests/test_calc.py
def test_add():
    assert 1 + 2 == 3

conftest.py

複数のテストで共通利用するfixtureはconftest.pyに置くことが多い。テスト側で通常のimportは不要。pytestが対象テストのディレクトリと親ディレクトリ側のconftest.pyを見つけて読み込む。

tests/
├── conftest.py
├── test_auth.py
└── test_users.py

fixtureの基本

fixtureはテスト前に必要な状態やデータを用意するために使う。テスト関数の引数名にfixture名を書けば受け取れる。

import pytest

@pytest.fixture
def user_data():
    return {
        "email": "test@example.com",
        "password": "password",
    }

def test_user_email(user_data):
    assert user_data["email"] == "test@example.com"

yieldで後始末

DB接続や一時ファイルなど、テスト後に片付けたいものはyieldを使うと分かりやすい。yieldより前がセットアップ、後ろがteardownになる。

@pytest.fixture
def db_session():
    session = create_test_session()
    try:
        yield session
    finally:
        session.rollback()
        session.close()

fixture scope

scopeはfixtureをどの範囲で使い回すかの指定。

scope 使い回す範囲 自分の使い分け
function 各テストごと 基本。DBトランザクションやログイン状態など
class テストクラス単位 同じクラスだけで共有するとき
module ファイル単位 そのテストファイル内で共有したい重い準備
package パッケージ単位 複数モジュールで共有したい場合
session テスト実行全体で1回 テスト用アプリ生成など本当に共有してよいもの

以前は「DBへ登録するfixtureはsessionにした方がよい」と考えていたが、これは使い方による。テストデータまでsessionで共有すると、テスト同士が状態に依存して失敗しやすい。今は各テストを独立させることを優先して、DBデータはfunction scope+ロールバック、またはテストごとに一意なデータを作る方を基本にする。

また、広いscopeのfixtureから狭いscopeのfixtureを直接利用するとScopeMismatchになるので注意。

parametrizeで入力パターンを増やす

同じロジックを複数の値で確認するときは@pytest.mark.parametrizeが便利。

import pytest

@pytest.mark.parametrize(
    "a,b,expected",
    [
        (1, 2, 3),
        (0, 5, 5),
        (-1, 1, 0),
    ],
)
def test_add(a, b, expected):
    assert a + b == expected

入力値と期待値を並べておけるので、APIのバリデーションテストでも使いやすい。

例外を確認する

エラーになること自体が正しい処理ならpytest.raisesを使う。

import pytest

def divide(a, b):
    if b == 0:
        raise ValueError("b must not be zero")
    return a / b

def test_divide_by_zero():
    with pytest.raises(ValueError, match="must not be zero"):
        divide(10, 0)

例外型だけでなくmatchでメッセージまで確認すると、意図したエラーか分かりやすい。

Flask APIでよく使う形

Flaskならテスト用appとclientをfixtureにしておくと使いやすい。

# conftest.py
import pytest
from app import create_app

@pytest.fixture(scope="session")
def app():
    app = create_app({"TESTING": True})
    return app

@pytest.fixture
def client(app):
    return app.test_client()
def test_health(client):
    response = client.get("/health")

    assert response.status_code == 200
    assert response.get_json()["status"] == "ok"

POSTやPUTでは、ステータスコードだけでなくJSONの値も合わせて確認する。

def test_create_user(client):
    response = client.post(
        "/users",
        json={"name": "Taro"},
    )

    assert response.status_code == 201

    data = response.get_json()
    assert data["name"] == "Taro"

浮動小数点はapprox

小数計算で完全一致を使いたくない場合はpytest.approxを使える。

def test_float():
    assert 0.1 + 0.2 == pytest.approx(0.3)

coverageを確認する

pytest-covを入れておけば、テストを実行しながらcoverageを確認できる。

# パッケージ全体
pytest --cov=app tests/

# 未実行行も表示
pytest --cov=app --cov-report=term-missing tests/

# HTMLレポート
pytest --cov=app --cov-report=html tests/

term-missingは未カバーの行番号が出るので、普段はこれが一番分かりやすい。coverageを100%にすること自体を目的にするより、重要な分岐や異常系が抜けていないかを見るために使う。

よく使うオプション

オプション 用途
-v テスト名を詳しく表示
-q 出力を簡潔にする
-s 標準出力をキャプチャしない
-k 名前でテストを絞り込む
-x 最初の失敗で停止
--maxfail=N N件失敗したら停止
--lf 前回失敗したテストを再実行
--tb=short トレースバックを短く表示

失敗したときに見るところ

pytestの失敗表示は、基本的に一番下のassert差分とテスト名を見る。例えばAPIで期待値が200なのに実際は201なら、実装が間違っているのか、テスト側の期待値が古いのかを確認する。

E       assert 201 == 200
FAILED tests/test_users.py::test_create_user

APIの作成成功なら201が正しいことも多いので、「テストが落ちた=実装が悪い」と決めつけず仕様と照らして確認する。

自分用の基本形

# 普段
pytest -q

# 失敗原因を詳しく見る
pytest -v -s tests/test_users.py::test_create_user

# 前回失敗だけ
pytest --lf

# coverage込み
pytest --cov=app --cov-report=term-missing tests/

テストコード自体が複雑になると修正コストも上がるので、fixtureは共有しすぎず、1つのテストでは確認したいことをなるべく絞る。

公式情報メモ

最終更新:2026年9月

タイトルとURLをコピーしました