0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pytest の基礎から Docker で実行、実践テストケースまで解説

Last updated at Posted at 2025-06-14

はじめに

「とりあえず動いたけど将来の改修で壊れない保証がない」
——そんな不安を解消する最短ルートが pytest です。

本記事では 代表的な機能を Docker 上でそのまま動くコード+実行結果付き でまとめました。

pytestの基本構成

.
├── app/
│   └── calc.py          # テスト対象
├── tests/
│   └── test_calc.py     # すべてのケースをここで実装
├── pytest.ini           # マーカー定義など
├── Dockerfile & docker-compose.yml
└── (任意) pyproject.toml / setup.py
app/calc.py
import math

def divide(x, y):
    return x / y

def save_to_file(path, content):
    with open(path, "w") as f:
        f.write(content)

def sin_deg(degree):
    return math.sin(math.radians(degree))

def buggy_feature(): 
    return 1 / 0

def notify_user(user, send_func):
    body = f"Hi {user}, welcome!"
    send_func(user, body)

テスト実行(Docker)

app/calc.py
docker compose up --build          # or: docker compose run --rm app

上記ソースを利用して、実際の代表的なケースを解説します。

通常のテスト (アサーション)

概要

assert を使うだけで、結果を検証できるのが pytest の最大の特徴です。

コード

tests/test_calc.py
def test_divide():
    assert calc.divide(10, 2) == 5
    assert calc.divide(10, 5) == 2

出力

pytest  | .                                                                        [100%]
pytest  | ================================ tests coverage ================================
pytest  | _______________ coverage: platform linux, python 3.11.13-final-0 _______________
pytest  | 
pytest  | Name          Stmts   Miss  Cover   Missing
pytest  | -------------------------------------------
pytest  | app/calc.py       4      0   100%
pytest  | -------------------------------------------
pytest  | TOTAL             4      0   100%
pytest  | 1 passed in 0.82s

解説

  • assert は Python 標準構文。pytest はそれを自動検出し、失敗時に 並べられた値の差 を表示します

  • 少ないコードで、検証を文脈として読めるテスト を書けます

  • 値の内容は以下の通り。

列項 意味
Stmts 課題コード内の Python 構文の行数
Miss テストで一度も通っていない行
Cover (実行済み行 / 全行) の割合
Missing 実行されなかった行番号 (複数の場合は列挙)

. fixture で共通化

概要

同じ前提データを何度も書きたくないときに、@pytest.fixtureで一括構築することで、重複するセットアップ が解決します

tests/test_calc.py
@pytest.fixture(params=[
    (10, 2, 5),
    (20, 4, 5),
    (-9, -3, 3),
])
def division_case(request):
    """(被除数, 除数, 期待値) を返す"""
    return request.param

def test_divide_fixture(division_case):
    x, y, expected = division_case
    assert calc.divide(x, y) == expected

解説

  • @pytest.fixture(params=...)で 複数のテストデータ を fixture から渡せます
  • division_case は、test 関数の実行ごとに1 セットずつ渡されます

parametrize で同型テストを一行に

概要の説明

「同じ関数ロジックを異なる入力で繰り返し検証したい」ときに最も力を発揮するのが @pytest.mark.parametrize です。
毎回テスト関数をコピーせずに、引数パターンを並べるだけでケース数を量産できます。

tests/test_calc.py
import pytest
from app import calc

@pytest.mark.parametrize(
    ("x", "y", "expected"),
    [
        (10, 2, 5),
        (20, 4, 5),
        (9, 3, 3),
        (-8, -2, 4),
        (0, 5, 0)
    ]
)
def test_divide_parametrize(x, y, expected):
    assert calc.divide(x, y) == expected

解説

  • 各テーブルは、一つの test 関数を同じ式で何度も実行します
  • 失敗した場合は「[90-3-3]で失敗」などと出力されるので検知が容易

fixture との違い

個人的に最初試していた時に、fixtureとの違いが不明確だったので、まとめてみました。

比較項目 parametrize fixture
関数呼び出し回数 ケース数ぶん繰り返す(= ループ自動展開 関数自体は1回だけ
結果の記録 各ケースが 別々のテスト結果として出る テスト関数単位で1回ぶんの結果
適用場面 「入力を網羅する」ことが目的のとき 共通の前提状態や環境が必要なとき

skip / xfail で予期された失敗を可視化

概要

未対応 OS や既知バグ を そのままスキップすることができます。

tests/test_calc.py
import pytest
from app import calc
import sys

@pytest.mark.skipif(sys.platform == "win32", reason="Windowsでは実行不可")
def test_not_on_windows():
    assert True  # ここではOKとする

@pytest.mark.xfail(reason="buggy_featureはまだ修正されていない")
def test_buggy_feature():
    calc.buggy_feature()
app/calc.py
import math
def buggy_feature():
    #既知のバグがある仮の機能
    return 1 / 0

出力結果

pytest  | .x                                                                       [100%]
pytest  | ================================ tests coverage ================================
pytest  | _______________ coverage: platform linux, python 3.11.13-final-0 _______________
pytest  | 
pytest  | Name          Stmts   Miss  Cover   Missing
pytest  | -------------------------------------------
pytest  | app/calc.py       5      0   100%
pytest  | -------------------------------------------
pytest  | TOTAL             5      0   100%
pytest  | 1 passed, 1 xfailed in 1.15s
pytest exited with code 0

詳細に解説

  • skip は その環境で実行したくないテストをとばす
  • xfail は 失敗することが予期されるテストで、修正されたら XPASS として告知される

設定ファイルによるカスタム

概要

pytest.ini で CLI オプションやマークを標準化 できます。
プロジェクト全体にルールやマークを適用でき、コマンドの簡略化や警告の抑制ができます。

app/calc.py
@pytest.mark.slow
def test_slow_calc():
    import time
    time.sleep(1.1)  # 1秒超え
    assert True
app/pytest.ini
[pytest]
addopts    = -q -ra
markers    =
    slow: mark tests as slow (>1s)

解説

  • addopts でコマンドのオプションを 毎回書かなくて済む
  • markers を記述すると、未登録マークの警告を消せる

モック・スタブ

概要

実際のメール送信や DB 書き込みなどを行わずに、関数の呼び出しと引数だけを検証する方法です。
外部依存(メール送信・DB など)を 切り離してロジックだけ検証。

test/test_calc.py
def test_notify_user(mocker):
    mock_sender = mocker.Mock()
    calc.notify_user("alice", send_func=mock_sender)
    mock_sender.assert_called_once_with("alice", "Hi alice, welcome!")
app/calc.py
def notify_user(user, send_func):
    #ユーザーに通知を送る(外部依存あり)
    body = f"Hi {user}, welcome!"
    send_func(user, body)

詳細に解説

  • mocker は pytest-mock パッケージの便利な fixture です
  • 本物の通信や I/O を使わず、「その関数が正しく呼ばれたか」だけを確認します
  • 安全で速く、CIでも安心して回せます

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?