採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

test ch03 — pytest mock

テクノロジ系 / L10 test — テスト

test ch03 — pytest mock

「外部に触らず」テストする最小単位

ゴール

  • unittest.mock で関数・メソッドを差し替えられる
  • monkeypatch で外部依存を切り離せる
  • MagicMockreturn_value / side_effect を使い分けられる

なぜ mock が要るか

# requests で外部 API を叩く関数
import requests

def fetch_user(user_id: int) -> dict:
    res = requests.get(f"https://api.example.com/users/{user_id}")
    return res.json()

テストの度に本物の API を叩くと:

  • 遅い (ネットワーク往復)
  • 不安定 (相手のダウンで落ちる)
  • 副作用 (課金 / レート制限)

→ 外部依存を「偽物」に差し替えるのが mock

最小例: unittest.mock.patch

# test_fetch.py
from unittest.mock import patch
from app import fetch_user

def test_fetch_user():
    with patch("app.requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"id": 1, "name": "Taro"}

        user = fetch_user(1)

        assert user == {"id": 1, "name": "Taro"}
        mock_get.assert_called_once_with("https://api.example.com/users/1")
  • patch("app.requests.get")app から見える requests.get を差し替える
  • return_value → 呼ばれた時に返す値
  • assert_called_once_with(...) → 期待した引数で呼ばれたか検証

どこを patch するか (頻出ハマり)

# app.py
from requests import get   # ← 直接 import している

def fetch():
    return get("...").json()
# ❌ ハマる
with patch("requests.get"): ...

# ✅ "使う側" を patch する
with patch("app.get"): ...

ルール: import された後の名前空間を patch する

pytest 流: monkeypatch

def test_fetch_user(monkeypatch):
    class FakeResponse:
        def json(self):
            return {"id": 1, "name": "Taro"}

    def fake_get(url):
        return FakeResponse()

    monkeypatch.setattr("app.requests.get", fake_get)

    user = fetch_user(1)
    assert user["name"] == "Taro"
  • pytest fixture なので with ブロック不要
  • テスト終了時に自動で元に戻る

MagicMock の便利機能

from unittest.mock import MagicMock

m = MagicMock()
m.return_value = 42            # 呼ばれたら 42 を返す
m()                            # → 42

m.side_effect = [1, 2, 3]      # 呼ぶ度に順番に返す
m(); m(); m()                  # → 1, 2, 3

m.side_effect = ValueError()   # 呼ばれたら例外を投げる
m()                            # → raises ValueError

# 呼ばれた回数 / 引数の検査
m.call_count                   # 呼ばれた回数
m.call_args                    # 最後の引数
m.assert_called_with(x=1)      # 期待引数で呼ばれたか

drill

drill/fetch_user のテストで requests.get を mock 化する

よくある詰まり

症状原因対処
mock したのに本物が呼ばれるpatch 先が間違い"使う側" の名前空間を patch
AttributeError: ... does not have the attributepatch 対象が存在しないimport path を見直す
return_value を chain したら MagicMock が返るmock の階層を assertion し忘れmock_get.return_value.json.return_value の階層に合わせる
2 回目の呼び出しで意図しない値return_value は毎回同じ違う値なら side_effect=[...]

まとめ

  • 外部依存 (API / DB / time / 乱数) はテストで mock する
  • patch は 使う側の名前空間 を指す
  • return_value / side_effect / assert_called_with が三種の神器
  • pytest なら monkeypatch fixture で短く書ける

次に学ぶ領域

領域続けて読む
FastAPI の TestClient で endpoint テストtest/ch04-testclient (予定)
DB を mock せず in-memory で済ますdb/ch??-sqlite-memory (予定)
fixture 設計pytest 公式 fixture ガイド
力試ししたい方は クイズ形式の演習(任意) もあります。読むだけでも十分です。