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を選びます。