---
title: "pytestの使い方と書き方｜fixture・parametrize・docstringの扱いまで【9.1対応】"
url: "https://www.issoh.co.jp/tech/details/7527/"
published: 2025-07-02
updated: 2026-09-27
categories: ["テスト"]
publisher: "株式会社一創"
---

# 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による環境構築とパッケージ管理](/tech/details/4384/)と[Poetry 2.4系の使い方とuvとの選定基準](/tech/details/12303/)で扱っています。`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` で確かめられます。外部サービスを差し替える場合の考え方は[モックとスタブの違いとテストダブルの使い分け](/tech/details/13505/)で整理しています。

## 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と目標設定](/tech/details/13507/)、CIでの集計は[CodecovのGitHub Actions連携](/tech/details/17720/)を参照してください。

テストを先に書いて実装を進める手順は、[TDDの基本サイクルと導入判断](/tech/details/8496/)で、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ページ](https://pypi.org/project/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を選びます。

## 関連記事

- [モックとは？スタブとの違い・テストダブル5分類と単体テストでの使い分けを実装者向けに解説](/tech/details/13505/)
- [テストカバレッジとは？C0/C1/C2の網羅率と計測ツール・目標設定を実装者向けに解説](/tech/details/13507/)
- [TDDとは？テスト駆動開発の基本サイクルと導入判断を実装者向けに解説](/tech/details/8496/)
- [uvとは？Pythonの環境構築・パッケージ管理・バージョン管理の使い方を徹底解説](/tech/details/4384/)

---

出典: [pytestの使い方と書き方｜fixture・parametrize・docstringの扱いまで【9.1対応】](<https://www.issoh.co.jp/tech/details/7527/>)（株式会社一創）
