定義した場所より、参照している場所を見る
関数をmockで置き換えたのに本物が呼ばれる場合は、patchする名前の場所を確認する。from provider import fetch_valueと読み込んだ関数は、利用側のモジュールにも別の名前として保持される。provider.fetch_valueを後から差し替えても、利用側がすでに持つ参照は変わらないことがある。実際に検索される利用側の名前をpatchするのが基本である。
以下はprovider.py、reporting.py、test_reporting.pyの3ファイルで再現する。providerは外部サービスの代わりに固定値を返す教材用の関数であり、誤ったpatchのテストでも通信は発生しない。間違った場所をpatchしたときの挙動、正しい差し替え、引数の契約、依存先の失敗を順に確認する。
利用側へpatchし、autospecで引数を確認する
provider.py
def fetch_value(sensor, *, unit="V"):
# A local fixture, not a real network request.
return 1.25
reporting.py
from provider import fetch_value
def report(sensor):
value = fetch_value(sensor, unit="V")
return f"{sensor}={value:.2f} V"
test_reporting.py
from unittest.mock import patch
import pytest
import reporting
def test_wrong_patch_location():
with patch("provider.fetch_value", return_value=9.0) as mocked:
assert reporting.report("A") == "A=1.25 V"
mocked.assert_not_called()
def test_correct_location_and_signature():
with patch("reporting.fetch_value", autospec=True, return_value=2.5) as mocked:
assert reporting.report("A") == "A=2.50 V"
mocked.assert_called_once_with("A", unit="V")
with pytest.raises(TypeError):
mocked("A", units="V")
with pytest.raises(TypeError):
mocked()
def test_dependency_error():
with patch("reporting.fetch_value", autospec=True, side_effect=TimeoutError("simulated")) as mocked:
with pytest.raises(TimeoutError, match="simulated"):
reporting.report("A")
mocked.assert_called_once_with("A", unit="V")
python -m pytest -q test_reporting.pyで実行すると3件が成功する。最初のテストは「誤ったpatchが使われないこと」を確認するテストなので、その観測が正しければ成功となる。差し替えに成功したという意味ではない。以下は集計部分の抜粋である。
実行結果(抜粋)
3 passed
importの形から参照先をたどる
report()の中にあるfetch_valueはreportingモジュールのグローバル名である。そこをpatch("reporting.fetch_value", …)すると、実行時に検索される値がmockになる。一方で、reporting.pyがimport providerと書き、provider.fetch_value(…)と呼ぶ設計なら、検索する場所もその形に沿って変わる。機械的にいつも同じ場所をpatchしない。
最初のテストでは戻り値9.0を設定したのに、report()は元の1.25を使う。mockのassert_not_called()が、この差し替えを通っていないことを確認している。本物がネットワークへつながるコードで同じ間違いをすると、テスト中に意図しない通信が起きるおそれがあるため、まず安全な小さな例で名前の解決を理解する。
autospecが防ぐことと、防がないこと
autospec=Trueにすると、置き換え対象の関数のシグネチャに基づいて引数を検査する。unitをunitsと間違えた呼び出しや、必須のsensorを省いた呼び出しをTypeErrorとして検出できる。単なる自由なmockは多くの引数を受け付けるため、本物なら失敗する呼び出しでもテストを通してしまうことがある。
autospecは値の意味まで検証するものではない。sensorが実在するか、unitに許可された単位が入っているか、返した値が測定範囲内かは別の検査が必要である。戻り値を2.5へ固定したことも、本物のサービスが同じ型を返すという証明にはならない。対象によっては属性の参照時に処理が走るため、複雑なオブジェクトを仕様として調べる場合にも注意する。
assert_called_once_with()は呼び出しが1回で、指定した引数を使ったことを確認する。戻り値がたまたま合うだけでは、同じ処理を何度も呼んでいないかは分からない。課金や時間のかかる依存先なら回数の確認が意味を持つ。一方、内部実装の細かな呼び出し順まで固定しすぎると、意味を変えない改善でもテストが壊れやすくなる。
依存先が失敗する場合も制御する
side_effectに例外を設定すると、そのmockが呼ばれた時点で例外になる。例では時間切れが呼び出し側へ伝わることと、どの引数で呼んだかを確認する。本物の待ち時間を発生させずに失敗時の分岐を検査できる。withを抜けるとpatchは元に戻るが、実際の外部操作を取り消す機能ではないので、差し替えの範囲は実行前に確認する。
実行環境と関連情報
掲載コードはLinux上のCPython 3.12.14で実行した。pytestを使う例はpytest 9.1.1で確認している。OS固有のファイル操作や対話環境の違いは、本文に記した条件に従って扱う。
関連:monkeypatchで環境変数や外部依存を切り替えてテストする / pytestで正常系と異常系の自動テストを始める
- Python公式:where to patch(2026年10月2日参照)
- Python公式:autospeccing(2026年10月2日参照)
