test ch03 — pytest mock
テクノロジ系 / L10 test — テスト
test ch03 — pytest mock
「外部に触らず」テストする最小単位
ゴール
unittest.mockで関数・メソッドを差し替えられるmonkeypatchで外部依存を切り離せるMagicMockのreturn_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 attribute | patch 対象が存在しない | 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 なら
monkeypatchfixture で短く書ける
次に学ぶ領域
| 領域 | 続けて読む |
|---|---|
| FastAPI の TestClient で endpoint テスト | test/ch04-testclient (予定) |
| DB を mock せず in-memory で済ます | db/ch??-sqlite-memory (予定) |
| fixture 設計 | pytest 公式 fixture ガイド |
力試ししたい方は クイズ形式の演習(任意) もあります。読むだけでも十分です。