テスト

pytestの使い方と書き方|fixture・parametrize・docstringの扱いまで【9.1対応】

pytestの使い方と書き方|fixture・parametrize・docstringの扱いまで【9.1対応】

pytestは、Pythonのテストを素の assert で書けるテストフレームワークです。2026年9月時点の最新版は 9.1.1(2026年6月19日公開・Python 3.10以上)で、9.0からは pyproject.toml の [tool.pytest] に設定をTOMLのまま書けるようになりました。この記事では、インストールからテストの書き方、fixture・parametrize・例外の検証、よく使うコマンドオプションまでを、pytest 9.1.1 で実際に動かしたコードで説明します。docstringが表示される場所と、doctestやJUnit XMLでの使い方も説明します。

まとめ:pytestの使い方の要点

  • 導入:pip install -U pytest または uv add --dev pytest。9.x は Python 3.10 未満では入りません。
  • 書き方:test_*.py か *_test.py のファイルに test で始まる関数を置き、assert で比較します。クラスでまとめるなら Test で始め、__init__ を定義しません。
  • 前処理と後処理:@pytest.fixture の中で yield し、その前後に書きます。共有したいfixtureは conftest.py へ置きます。
  • 入力の組み合わせ:@pytest.mark.parametrize で1関数を複数ケースに展開します。
  • 例外:with pytest.raises(ValueError, match=...)。match は部分一致の正規表現です。
  • docstring:-v を付けてもテスト関数のdocstringは表示されません。docstringが効くのは --fixtures の説明、--doctest-modules による実行、フックで書き出すレポートの3か所です。

以下、導入から順に手を動かせる形で説明します。

pytest 9.1のバージョンとインストール

PyPI上の最新版は 9.1.1 で、対応Pythonは requires_python >=3.10 です。9.0.0 でPython 3.9のサポートが終了したため、Python 3.9 の環境で pip install pytest を実行すると、条件を満たす 8.4.2 が入ります。9.x の機能を使う記事やCI設定を読むときは、まず手元の版を確かめてください。

版 PyPI公開日 主な変化
8.4.2 2025-09-04 Python 3.9 で使える最終版
9.0.0 2025-11-08 3.9対応終了・ネイティブTOML設定・strictモード・subtests
9.1.0 2026-06-13 --max-warnings 追加・Python 3.15 公式対応
9.1.1 2026-06-19 9.1.0 の回帰修正(conftest.py の読み込み等)

pip・uv・Poetryでのインストール

pytestは開発時だけ使う依存なので、プロジェクトの仮想環境に入れ、本番の依存とは分けて管理します。

# pip(仮想環境を有効化してから)
python -m pip install -U pytest

# uv(dev依存として pyproject.toml に記録)
uv add --dev pytest

# Poetry(testグループに記録)
poetry add --group test pytest

# 版の確認
pytest --version
# pytest 9.1.1

uvとPoetryの違いや選び方は、uvによる環境構築とパッケージ管理とPoetry 2.4系の使い方とuvとの選定基準で扱っています。pytest コマンドが見つからない、または別のPythonのpytestが動く場合は、python -m pytest で起動すると、そのPythonに入っているpytestを確実に使えます。

9.0で変わった設定:[tool.pytest]とstrict

8.x までの pyproject.toml では [tool.pytest.ini_options] に書き、既存のINI設定に対応する形で値を指定していました。設定項目によってはTOMLの配列も利用できました。9.0 からは [tool.pytest] テーブルに、リストや真偽値をTOMLの型のまま書けます。pyproject.toml を使わないプロジェクトなら、pytest.toml の [pytest] テーブルでも同じ設定ができます。

# pyproject.toml(pytest 9.1.1 で動作確認)
[tool.pytest]
minversion = "9.0"
testpaths = ["tests"]
pythonpath = ["src"]
addopts = ["-ra"]
strict = true
markers = ["slow: 実行に時間がかかるテスト"]

strict = true は、strict_config・strict_markers・strict_xfail・strict_parametrization_ids をまとめて有効にします。たとえば markers に登録していない @pytest.mark.slow を書くと、'slow' not found in `markers` configuration option で収集が止まります。マーカー名のタイプミスでテストが選択から漏れる事故を防げます。ただし公式の変更履歴は、pytestが今後追加する厳格化オプションも自動で有効になるため、pytestの版を固定(ロック)している場合に限って使うよう注意しています。版を固定しないなら、strict_markers = true のように個別に指定する方が、アップデートで突然テストが止まる事態を避けられます。

pytestのテストの書き方と命名規則

テストとして集められるファイル・クラス・関数

pytestは、既定では次の条件に合うものだけをテストとして収集します。名前が合っていないテストはエラーにならず、黙って無視されます。

対象 既定の条件 設定項目
ファイル test_*.py または *_test.py python_files
クラス Test で始まり __init__ を持たない python_classes
関数・メソッド test で始まる python_functions

クラスに __init__ を書くと、中のテストは実行されず、PytestCollectionWarning: cannot collect test class 'TestWithInit' because it has a __init__ constructor という警告だけが出ます。テストが「全部成功した」ように見えて実は走っていない、という状況になりやすいので、警告欄は読み飛ばさないでください。

assertの書き方と失敗時の表示

比較には assertEqual のような専用メソッドを使わず、Python標準の assert を書きます。pytestはテストモジュールのインポート時にassert文を書き換え、失敗時に左右の値や差分を表示します。辞書の比較が失敗したときの出力は次のとおりです。

def test_dict():
    assert {"a": 1, "b": 2} == {"a": 1, "b": 3}
E   AssertionError: assert {'a': 1, 'b': 2} == {'a': 1, 'b': 3}
E     Omitting 1 identical items, use -vv to show
E     Differing items:
E     {'b': 2} != {'b': 3}

一致しているキーは省かれ、異なるキー b だけが示されます。全体を見たいときは -vv を付けます。

ディレクトリ構成とクラスでのまとめ方

パッケージを src/ 配下に置き、テストを tests/ に分ける構成が扱いやすいです。前節の設定の pythonpath = ["src"] により、インストールしなくても from calc import divide で読み込めます。

proj/
├── pyproject.toml
├── src/calc/__init__.py
└── tests/
    ├── conftest.py
    └── test_calc.py

テスト対象のコードと、ここから先の例で使うテストは次のとおりです。

# src/calc/__init__.py
def divide(a, b):
    """a を b で割る。

    >>> divide(6, 3)
    2.0
    """
    return a / b


def parse_age(text):
    """文字列を年齢に変換する。負数は ValueError。"""
    value = int(text)
    if value < 0:
        raise ValueError(f"age must be >= 0, got {value}")
    return value
# tests/test_calc.py
import pytest
from calc import divide, parse_age


def test_divide():
    """6 / 3 が 2.0 になること。"""
    assert divide(6, 3) == 2.0


class TestDivide:
    def test_zero(self):
        with pytest.raises(ZeroDivisionError):
            divide(1, 0)

クラスは関連するテストを束ねる名前空間として使うもので、unittest.TestCase を継承する必要はありません。setUp の代わりにfixtureを使います。

pytestとdocstring:表示される場所と使い道

「テスト関数に書いたdocstringを実行結果に出したい」という目的でpytestを調べる人は多いのですが、pytest 9.1.1 の標準出力には、-v を付けてもテスト関数のdocstringは表示されません。-v で出るのは次のようなテストIDです。

tests/test_calc.py::test_divide PASSED                                   [ 14%]
tests/test_calc.py::test_parse_age_ok[zero-padded] PASSED               [ 57%]
tests/test_calc.py::TestDivide::test_zero PASSED                         [100%]

テストの意図を実行結果で読み取らせたいなら、docstringより先に関数名そのものを説明的にするのが確実です(例:test_parse_age_rejects_negative)。そのうえで、docstringがpytestで意味を持つのは次の3か所です。

fixtureのdocstringと–fixturesでの説明表示

pytest --fixtures は使えるfixtureの一覧を表示し、各fixtureのdocstringを説明として並べます。表示されるのはdocstringの最初の段落で、-v を併用すると全文が出ます。docstringの無いfixtureには no docstring available と表示されます。

------------------------ fixtures defined from conftest ------------------------
no_doc_fixture [session scope] -- tests/conftest.py:14
    no docstring available

records -- tests/conftest.py:5
    一時ディレクトリに3行のCSVを置き、そのパスを返す。

チームで共有する conftest.py のfixtureには、何を返し、後処理で何を消すのかを1行で書いておくと、この一覧がそのまま利用ガイドになります。テスト関数よりfixtureのdocstringに手間をかける方が、読む人に届きます。

docstring内の対話例とdoctestの実行範囲

docstringに >>> で書いた対話例は、--doctest-modules を付けるとテストとして実行されます。上の divide の例は1件のテストとして数えられます。

$ pytest --doctest-modules src tests
src/calc/__init__.py .                                                   [ 12%]
tests/test_calc.py .......                                               [100%]
============================== 8 passed in 0.07s ===============================

注意点が2つあります。1つ目に、testpaths = ["tests"] を設定したまま引数なしで pytest --doctest-modules を実行すると、src は探索範囲に入らず、doctestは1件も集まりません(実測では tests 配下の7件だけが走りました)。doctestを回すなら testpaths に src も加えるか、上のようにパスを明示します。2つ目に、9.1.0 の変更履歴によると、--doctest-modules 使用時にテストモジュール内で直接定義した module・package・session スコープのautouse fixtureが2回実行されることがあります。避けたい場合はそのfixtureを conftest.py へ移します。

docstringのJUnit XMLへの出力

CIのレポートにテストの説明を載せたい場合は、conftest.py のフックでdocstringの1行目を user_properties に入れると、--junitxml の出力に property として書き出されます。ただし、testcase配下へのproperties追加は一部のJUnit XMLスキーマ検証に適合しないため、利用するCIのレポート取り込み機能で受け付けられるか確認が必要です。

# tests/conftest.py に追記
def pytest_collection_modifyitems(items):
    for item in items:
        func = getattr(item, "function", None)
        doc = (func.__doc__ or "").strip() if func else ""
        if doc:
            item.user_properties.append(("docstring", doc.splitlines()[0]))
$ pytest tests --junitxml=report.xml
# report.xml の該当部分
<testcase classname="tests.test_calc" name="test_divide" time="0.001">
  <properties><property name="docstring" value="6 / 3 が 2.0 になること。" /></properties>
</testcase>

docstringの無いテスト(ここでは parametrize した test_parse_age_ok など)には何も付きません。

pytestの実行方法とよく使うオプション

引数なしの pytest は、設定ファイルの testpaths(未設定ならカレントディレクトリ)以下を探索して全テストを実行します。対象を絞る書き方と、よく使うオプションは次のとおりです。

やりたいこと コマンド
1ファイルだけ pytest tests/test_calc.py
1関数だけ pytest tests/test_calc.py::test_divide
名前の部分一致で絞る pytest -k "divide and not zero"
マーカーで絞る pytest -m "not slow"
出力を簡潔に・詳細に pytest -q / pytest -v
最初の失敗で止める pytest -x(N件で止めるなら --maxfail=N)
前回失敗したものだけ pytest --lf
print出力を表示 pytest -s
トレースバックを短く pytest --tb=short
遅いテストを表示 pytest --durations=5
集めるだけで実行しない pytest --collect-only

print出力の表示:-s と –capture

pytestはテスト中の標準出力を捕捉し、成功したテストの print は表示しません。失敗したテストの出力だけが「Captured stdout call」として出ます。成功時も見たいときは -s を付けます。-s は --capture=no の短縮形です。

# tests/test_print.py
def test_print():
    print("debug: value=42")
    assert 6 / 3 == 2.0
$ pytest -q -s -k print
debug: value=42
.
# 以下、成功件数の表示は省略

ロガーの出力を確かめたい場合は、print を仕込むより caplog fixtureで記録を検証する方が、テストとして残せます。

–tb=shortの失敗表示と–durationsの所要時間表示

--tb には auto(既定)・long・short・line・native・no を指定できます。失敗が多いときは --tb=short でトレースバックを短縮すると、どこで落ちたかを一覧しやすくなります。--durations=N は遅い順にN件を表示し、0.005秒未満は既定で省かれます。

$ pytest -q --tb=short --durations=2
test_w.py:21: in test_dict
    assert {"a": 1, "b": 2} == {"a": 1, "b": 3}
E   AssertionError: assert {'a': 1, 'b': 2} == {'a': 1, 'b': 3}
...
============================= slowest 2 durations ==============================
0.45s call     test_w.py::test_slow

fixtureによる前処理・後処理の共通化

fixtureは、テストが使う値や環境を用意する関数です。テスト関数の引数にfixtureの名前を書くと、pytestが実行前に呼び出して結果を渡します。setUp/tearDown と違い、必要なテストだけが必要なものを引数で受け取るので、依存関係がシグネチャに表れます。

yieldによるfixtureの前処理・後処理

yield より前が前処理、後ろが後処理です。テストが失敗しても後処理は実行されます。組み込みの tmp_path fixtureを使えば、テストごとに別の一時ディレクトリが用意されます。

# tests/conftest.py
import pytest


@pytest.fixture
def records(tmp_path):
    """一時ディレクトリに3行のCSVを置き、そのパスを返す。"""
    path = tmp_path / "records.csv"
    path.write_text("id,age\n1,20\n2,31\n", encoding="utf-8")
    yield path
    print("teardown:", path.name)
# tests/test_calc.py
def test_records(records):
    lines = records.read_text(encoding="utf-8").splitlines()
    assert len(lines) == 3

scopeの5種類と選び方

scope は、fixtureを何回作り直すかを決めます。

scope 作り直す単位 向く用途
function(既定) テスト関数ごと 書き換えるデータ
class テストクラスごと クラス内で読むだけの値
module テストファイルごと ファイル単位の接続
package パッケージごと ディレクトリ単位の準備
session セッション単位で共有 起動の重いDBやサーバー

広いscopeは速くなる代わりに、あるテストの変更が別のテストへ漏れます。テストが単独では通るのに一括実行で落ちるときは、書き換えられる値を session や module で共有していないかを最初に疑ってください。autouse=True を付けると、そのfixtureを参照できる範囲のテストに、引数で要求しなくても適用されますが、どのテストが何に依存しているかが見えなくなるので、ログ設定や環境変数の固定などに限って使います。

conftest.pyによるfixtureの共有範囲

conftest.py に定義したfixtureは、そのディレクトリと配下のテストからimportなしで使えます。tests/conftest.py なら全テスト、tests/api/conftest.py なら api 配下だけが対象です。テストから直接 conftest をimportしないでください。どのfixtureがどこで定義されているかは、前章の pytest --fixtures で確かめられます。外部サービスを差し替える場合の考え方はモックとスタブの違いとテストダブルの使い分けで整理しています。

parametrizeによる複数ケースへの展開

@pytest.mark.parametrize は、引数名と値のリストを渡し、ケースごとに独立したテストを生成します。1ケースが失敗しても残りは実行されるので、ループで回すより失敗箇所が分かりやすくなります。

@pytest.mark.parametrize(
    ("text", "expected"),
    [("0", 0), ("42", 42), pytest.param("007", 7, id="zero-padded")],
)
def test_parse_age_ok(text, expected):
    assert parse_age(text) == expected

テストIDは既定で値から作られ(test_parse_age_ok[0-0])、pytest.param(..., id=...) で名前を付けられます。pytest.param には marks=pytest.mark.xfail のように個別のマーカーも渡せます。値が長い文字列や辞書になる場合はIDが読みにくくなるので、id を付けておくと -k zero-padded で1ケースだけを再実行できます。9.x の strict_parametrization_ids を有効にすると、IDが重複したときにエラーになります。9.1.0 からは、argvalues にジェネレータのような使い切りのイテラブルを渡すことが非推奨になりました。リストかタプルで渡してください。

ケースが数百件に増えるなら、CSVやJSONから値を読み込んでparametrizeへ渡す方法もあります。ただし、ファイルの中身を変えるとテスト数が変わるため、どのケースが増減したかをレビューで追いにくくなります。境界値や代表値を選んで数件に絞る方が、多くの場合は保守しやすいです。入力を自動生成して性質を検証したい場合は、プロパティベーステストの方が向いています。

pytest.raisesによる例外型とメッセージの検証

例外が発生することを確かめるには、with pytest.raises(例外クラス) のブロック内で対象を呼びます。例外が出なければテストは失敗します。

def test_parse_age_negative():
    with pytest.raises(ValueError, match=r"got -1$"):
        parse_age("-1")

match は例外メッセージに対する re.search なので、部分一致で通ります(match="age" は "invalid age: -1" に一致します)。メッセージ全体を固定したい場合は ^ と $ で囲み、. や ( を含む文字列は re.escape を通してください。as excinfo で受け取ると、excinfo.value から例外オブジェクトの属性も検証できます。

基底クラスを指定すると、そのサブクラスも一致します。pytest.raises(Exception) と書くと、タイプミスによる NameError まで「期待どおり」と判定されるので、できるだけ具体的な例外クラスを指定します。

pytestとunittestの比較と移行判断

unittestは標準ライブラリで追加インストールが要らず、pytestは外部パッケージですが、書き方と機能に差があります。

観点 pytest unittest
導入 pip等で追加 標準ライブラリ
比較 素の assert assertEqual 等のメソッド
テストの形 関数でもクラスでも可 通常はTestCaseを継承
前処理 fixture(引数で注入) setUp/tearDown
パラメータ化 parametrize(9.0から subtests も) subTest
相手側の実行 unittestのテストも実行可 pytestのテストは実行不可

新規にテストを書くならpytestを選んでよいです。既存のunittestのテストはpytestでそのまま実行できるため、書き直さずにランナーだけ切り替え、新しいテストからpytest流で書く移行が現実的です。

ただし、unittest.TestCase のサブクラスの中ではpytestの機能が一部効きません。TestCase のメソッドに @pytest.mark.parametrize を付けると、pytest 9.1.1 では TypeError: TestLegacy.test_param() missing 1 required positional argument: 'n' で失敗しました。fixtureを引数で受け取ることもできません。parametrizeやfixtureを使いたいテストは、TestCase を継承しない関数かクラスへ書き直してください。

逆にunittestのままにしておく方がよいのは、外部パッケージを入れられない配布物の自己テストや、標準ライブラリだけで完結させたい小さなスクリプトです。

pytestの運用で詰まりやすい箇所

  • テストが0件になる:ファイル名が tests.py や check_calc.py だと既定の python_files に一致しません。pytest --collect-only -q で何が集まっているかを先に確認します。
  • ModuleNotFoundError でimportできない:src構成なら pythonpath = ["src"] を設定するか、pip install -e . でパッケージとして入れます。どの設定ファイルが読まれたかは、実行結果の先頭の rootdir: と configfile: で分かります。
  • 単独では通り一括で落ちる:まず、広いscopeのfixtureや、モジュール変数・環境変数の書き換えがテスト間で共有されていないか確認します。環境変数の変更は monkeypatch.setenv を使えば、テスト終了時に元へ戻ります。
  • カバレッジの数字だけを目標にする:pytest-cov で計測はできますが、行を通っただけでは期待値の検証になりません。指標の読み方はテストカバレッジのC0/C1/C2と目標設定、CIでの集計はCodecovのGitHub Actions連携を参照してください。

テストを先に書いて実装を進める手順は、TDDの基本サイクルと導入判断で、pytestを使う前提で説明しています。

よくある質問

pytestのテスト関数にdocstringを書くと実行結果に表示されますか?

表示されません。pytest 9.1.1 では -v でもテストIDだけが出ます。docstringが表示されるのは pytest --fixtures のfixture説明で、>>> の例は --doctest-modules で実行されます。レポートに載せたい場合は、フックで user_properties に入れて --junitxml へ書き出します。

pytestの最新バージョンはいくつですか?

2026年9月時点では 9.1.1(2026年6月19日公開)で、Python 3.10以上が必要です。手元の版は pytest --version で確認できます。最新版はPyPIのpytestページで確認してください。

pip install pytestでインストールできないときは?

まず python -m pip install -U pytest のように、使いたいPythonを明示して実行してください。Python 3.9 では9.x系は入らず、Python 3.9 に対応する最終版の 8.4.2 が入ります。それより古いPythonでは入る版がさらに下がります。社内プロキシ環境では、pip の --proxy 指定や証明書の設定が必要な場合があります。

pytestでクラスを使うときの書き方は?

クラス名を Test で始め、__init__ を定義せず、メソッド名を test で始めます。unittest.TestCase の継承は不要で、継承するとparametrizeや引数によるfixtureが使えなくなります。

pytestとunittestはどちらを使うべきですか?

新規ならpytestです。素の assert、fixture、parametrize が使え、既存のunittestのテストもpytestからそのまま実行できます。外部依存を増やせない場合や、既存のunittest構成を維持したい場合はunittestを選びます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.15 テックブログ リゾートトラスト不正アクセス:会員2.6万件・カハラ横浜3.6万件の対象確認と窓口
  2. 2026.10.16 テックブログ ソースネクスト不正アクセスで最大2,252万件流出|ログインできない理由・パスワード再設定と会員APIの点検
  3. 2026.10.16 テックブログ バーガーキング不正アクセスで約1,118万件の情報漏洩|対象確認・案内メールの真偽と運営側のログ点検
  4. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更の手順
  5. 2026.10.16 テックブログ ワラウ不正アクセスで連携トークン流出|Yahoo! JAPAN ID再連携と対象確認

RELATED POSTS 関連記事

目次