Python でのモッキング:unittest.mock、patch、およびテストを嘘にする間違い

  1. ホーム
  2. ブログ
  3. テストの自動化

テストの自動化

Python でのモッキング: unittest.mock、patch、およびテストを嘘にする間違い

モックを使用すると、実際のネットワーク、データベース、またはクロックを使用せずにコードをテストできます。不用意に使用すると、壊れたコードが通過してしまうこともあります。ここでは、実行例ごとに、unittest.mock を上手に使用する方法を示します。

読了目安8分
Python でのモック:unittest.mock、patch、そしてテストを偽らせる間違い

ポイント

  • モック は API クライアントなどの実際のオブジェクトの代わりとなるため、テストはオフラインで高速に実行されます。
  • 固定された答えには return_value を使用し、エラーまたはシーケンスには side_effect を使用します。
  • 名前が定義されている場所ではなく、使用されている場所に名前をパッチします。これは最も一般的なバグです。
  • spec または autospec を使用すると、タイプミスや間違った引数がサイレントに渡される代わりに失敗します。

関数は天気 API を呼び出します。テストはインターネット、API キー、明日の天気に依存すべきではありません。 Python の組み込み unittest.mock を使用すると、その呼び出しを、選択したものを正確に返す代用に置き換えることができます。

私たちは小さな weather モジュールと 9 つのテストを作成しました。これらはすべて Python 3.13 のユニットテストで実行されます。 9 つすべてが合格し、以下の印刷出力はその実行からのものです。

なぜ嘲笑するのでしょうか?

  • 速度: ネットワーク呼び出しやデータベースのセットアップはありません。
  • 信頼性: サードパーティのサービスがダウンしているためにテストが失敗することはありません。
  • コントロール: タイムアウト、エラー、奇数データをオンデマンドでシミュレートできます。
  • 分離: 他人のサーバーではなく、自分のロジックをテストします。

モックは主に単体テストに属します。統合テストでは、依然として実際の、または現実的な依存関係を扱う必要があります。

基本を模擬する

from unittest.mock import Mock

api = Mock()
api.get_price.return_value = 499

print(api.get_price("pen"))                # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list)        # [call('pen')]
print(api.anything_at_all())               # <Mock name='mock.anything_at_all()' ...>

Mock はすべての通話を記録するため、通話がどのように使用されたかを確認できます。ただし、最後の行に注目してください。プレーンなモックは、要求されたあらゆる属性を発明します。この柔軟性は便利ですが、後でわかるように危険でもあります。

MagicMock も同じアイデアですが、__len__ や __iter__ のような「マジック」メソッドもサポートしています。 patch() はデフォルトで MagicMock を使用します。

副作用: エラーとシーケンス

side_effect は、各呼び出しで何が起こるかを制御します。失敗をシミュレートするには、これを例外に設定します。

api.fetch.side_effect = ConnectionError("down")
api.fetch()   # raises ConnectionError

連続した呼び出しで異なる結果を返すには、これをリストに設定します。これは、再試行ロジックをテストするのに最適です。

def fetch_with_retry(api, attempts=3):
    for _ in range(attempts):
        try:
            return api.fetch()
        except ConnectionError:
            continue
    raise ConnectionError("all attempts failed")

api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api))    # {'ok': True}
print(api.fetch.call_count)     # 2

最初の呼び出しは失敗し、2 番目の呼び出しは成功しました。テストでは、関数が 1 回だけ再試行されたことが証明されました。これを実際のサービスに対してシミュレートすることはほぼ不可能です。

patch(): 一時的に何かを置き換えます

多くの場合、依存関係は渡されません。依存関係はモジュール内にインポートされます。 patch() テスト中にそれを交換し、その後復元します。

# weather.py
from http_client import get as requests_get

def report(city):
    data = requests_get(f"https://api.example.com/weather?city={city}")
    return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch

with patch("weather.requests_get") as fake_get:
    fake_get.return_value = {"temp_c": 31}
    print(weather.report("Pune"))   # Pune: 31°C
    fake_get.assert_called_once_with(
        "https://api.example.com/weather?city=Pune")

patch は、このようにコンテキスト マネージャーとして使用することも、モックを引数として渡すデコレーターとして使用することもできます。

定義されている場所ではなく、使用されている場所にパッチを適用します

これはほとんどの人が一度は犯す間違いです。実際の get 関数は http_client に存在するため、そこにパッチを適用するのが自然です。

with patch("http_client.get") as fake_get:
    fake_get.return_value = {"temp_c": 99}
    weather.report("Pune")

# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune

実際の関数が実行されました。 weather.py が from http_client import get as requests_get を実行すると、元の関数を指す独自の名前が作成されました。 http_client.get にパッチを適用すると別の名前が変更されたため、weather ではモックが表示されませんでした。私たちのhttp_client.getは意図的にレイズしたので、ミスは大きかったです。実際のプロジェクトでは、ライブ API を静かに呼び出す可能性があります。

ルール: "module_under_test.name" と書かれたテスト対象モジュールの名前にパッチを適用します。モジュールが import http_client を実行して http_client.get() を呼び出した場合、検索は呼び出し時に行われるため、パッチ "http_client.get" は正しいです。

spec と autospec: モックを厳密にする

プレーンなモックはタイプミスも含めて何でも受け入れます。仕様では、実際のクラスが持つ属性のみが許可されます。

api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'

loose = Mock()
loose.get_temprature("Pune")    # returns a Mock, no error at all

緩いモックはスペルミスのメソッドを喜んで受け入れたので、実際のコードがクラッシュしても、それを使用したテストは合格できました。 autospec はさらに進んでメソッドのシグネチャをチェックします。

with patch("weather.WeatherClient", autospec=True) as Fake:
    client = Fake.return_value
    client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments

独自のクラスをモックする場合は、autospec=True または create_autospec() を選択してください。実際のコードが変更されてもモックを正直に保ちます。

日付と時刻を偽装する

今日の日付に依存するコードはテストが困難です。クロックを小さな関数でラップし、次のパッチを適用します。

def today():
    return datetime.date.today()

def is_weekend():
    return today().weekday() >= 5

@patch("weather.today")
def test_date(self, fake_today):
    fake_today.return_value = datetime.date(2026, 11, 7)
    print(weather.is_weekend())   # True (a Saturday)

組み込み型では属性を設定できないため、datetime.date.today に直接パッチを適用することは簡単ではありません。フリーズガンやタイムマシンなどのライブラリは、これをより完全に処理します。

モックに関するアサーション

方法 小切手
assert_called() 少なくとも 1 回は呼び出されました
assert_called_once() 正確に 1 回呼び出されました
assert_called_with(...) 最後の呼び出しではこれらの引数が使用されました
assert_called_once_with(...) これらの引数を使用した呼び出しは 1 回のみ
assert_any_call(...) すべての呼び出しでこれらの引数が使用されました
assert_not_called() 一度も電話をかけなかった

アサーションが失敗した場合、メッセージは明らかです。決して実行されなかったメソッドに対して assert_called_once() を呼び出しました。

AssertionError: Expected 'save' to have been called once. Called 0 times.

古い Python バージョンでは、assret_called_once などのスペルミスのあるアサーション メソッドが黙って渡されます。最新のバージョンでは、assert で始まる名前や一般的なスペルミスに対してエラーが発生しますが、依然として仕様がより強力なガードです。

pytestとモック

上記はすべて pytest でも機能します。多くの pytest ユーザーは、単純な置換に組み込みの monkeypatch フィクスチャ、またはパッチを自動的に元に戻す mocker フィクスチャを提供する pytest-mock プラグインも使用します。 「使用されるパッチ」ルールはすべてに適用されます。フィクスチャとセットアップについては、pytest チュートリアルを参照してください。

嘲笑してはいけないとき

  • テストしているものをモックしないでください。 モックがすべての作業を行う場合、テストは何も証明しません。
  • 単純な値のオブジェクトをモックすることは避けてください。 本物を使用してください。
  • すべてをモックしないでください。 モックでいっぱいのテストは、動作が正しい場合でも、コードがリファクタリングされるたびに中断することがよくあります。
  • 実際の統合テストをいくつか保持します。 モックはサービスに関する想定をエンコードしますが、それらの想定は間違っている可能性があります。

また、モックされたパスは実際の動作を行わずに実行されるため、コード​​ カバレッジも容易に拡張されます。

よくある嘲笑の間違い

  • 間違った名前にパッチを適用する。 上記で説明しましたが、依然として「モックが無視される」最大の原因です。
  • return_value の設定を忘れています。 テスト対象のコードは、データの代わりに Mock オブジェクトを受け取ります。たとえば、結果が正しいかどうかだけをチェックする場合など、関数をそのまま通過してテストが偶然合格してしまう可能性があります。
  • 結果ではなくモックに対するアサート。 save() が呼び出されたことを確認することは、保存された内容を確認することよりも弱いです。 assert_called_once_with(...) を実引数とともに使用するか、関数の結果に対してアサートする方が適切です。
  • パッチの漏洩。 stop() を指定せずに patcher.start() を呼び出すと、後のテストのためにフェイクがそのまま残ります。コンテキスト マネージャー、デコレーター、または addCleanup(patcher.stop) を使用します。
  • デコレータが間違った順序で積み重ねられています。 複数の @patch デコレータがある場合、一番下のデコレータが最初の引数として渡されます。これらを黙って混合すると、間違ったモックが構成されてしまいます。
  • 多くのテストにわたって 所有していないライブラリをモックします。それを独自の小さなクラスでラップし、代わりにそれをモックします。テストでは、ライブラリの API ではなく、ニーズを説明します。

モック、スタブ、偽物

これらの単語は曖昧に使用されることがよくありますが、その違いはテストについて議論するときに役立ちます。 スタブは、定型的な回答を返すだけです。 モックも通話を記録するので、どのように使用されたかを確認できます。 偽は、データベースの代わりにメモリ内のリポジトリなど、機能する簡素化された実装です。 Python の Mock はスタブまたはモックとして機能します。フェイクは自分で作成する通常のクラスであり、正確な呼び出しではなく動作をチェックするため、多くの場合、リファクタリングに耐えるテストにつながります。

経験則としては、システムのエッジ (ネットワーク、時計、乱数、ファイル システム、有料のサードパーティ サービス) をモックすることです。それらのエッジの内側では、実際のオブジェクトを優先します。この方法で構築されたテストは高速なままであり、実際に作成したコードを引き続き実行します。

まとめ

モッキングは正確なツールです。遅い依存関係や予測不可能な依存関係を置き換え、依存関係が返すものを制御し、コードで依存関係がどのように使用されているかを確認します。 2 つの習慣があれば、ほとんどの問題を防ぐことができます。テスト対象のモジュールの名前にパッチを適用し、autospec を使用して、実際のオブジェクトが拒否する呼び出しをモックが拒否するようにします。

よくある質問

Python におけるモックとは何ですか?

モックは、API クライアントやデータベースなどの実際のオブジェクトを、テスト中に制御する偽のオブジェクトに置き換えます。 Python では、unittest.mock モジュールにこれが含まれています。

モックとマジックモックの違いは何ですか?

MagicMock は、__len__、__iter__、__enter__ などのマジック メソッドもサポートするモックです。 patch() はデフォルトで MagicMock を作成します。

私のパッチが機能しないのはなぜですか?

通常は、オブジェクトが使用される場所ではなく、オブジェクトが定義されている場所にパッチを適用したためです。テスト対象のモジュール内の名前にパッチを適用します。

return_valueとside_effectの違いは何ですか?

return_value は固定の結果を設定します。 Side_effect は、例外を発生させたり、リストから順番に値を返したり、関数を呼び出したりすることができます。

オートスペックは何をするのですか?

実際のオブジェクトからモックを構築するため、実際のコードの場合と同様に、欠落している属性や間違った引数があるとエラーが発生します。

学び続ける

Stack Quoraと連携する

テスト、WordPress、または SEO についてサポートが必要ですか?

私たちは、チームがより優れたソフトウェアを出荷し、サイトを成長させるのを支援します。あなたが取り組んでいることを教えてください。明確な次のステップを提案します。

読み続けてください

詳しいガイドはこちら テスト、AI、最新の開発

この記事は気に入りましたか?トピックを選択して、最新のチュートリアルに直接アクセスしてください。

人工知能

AI 幻覚の説明: 言語モデルが物事をでっち上げる理由とそれを軽減する方法

AI 幻覚について説明しました。幻覚とは何か、言語モデルが幻覚を生成する理由、一般的な種類、グラウンディングとプロンプトで幻覚を軽減する方法、そして私たちが実行した簡単なチェックです。

記事を読む →
DevOps

環境変数の説明: .env ファイル、シークレット、および設定が正しく行われている

実行した Python の例を使用して環境変数について説明します。環境変数の読み取りと設定、すべての値が文字列である理由、.env ファイル、プロセスの継承、シークレット、検証について説明します。

記事を読む →
ソフトウェア開発

API レート制限の説明: アルゴリズム、ヘッダー、および 429 エラーの処理

API レート制限については、実行したシミュレーションで説明します。トークン バケット、固定ウィンドウ、スライディング ウィンドウ、429 応答、レート制限ヘッダー、バックオフによる再試行、およびテスト制限です。

記事を読む →
テスト自動化

Python でのモッキング:unittest.mock、patch、およびテストを嘘にする間違い

Python でのunittest.mockによるモック、テスト済み: モックとreturn_value、side_effect、名前が使用されている場所のパッチ、仕様と自動仕様、日付のモック、およびよくある間違い。

記事を読む →
ソフトウェアテスト

コードカバレッジの説明: 回線カバレッジとブランチカバレッジ、および 100% の実際の意味

実際のcoverage.pyの実行でコードカバレッジを説明: 行、分岐、条件のカバレッジ、レポートの読み取り、100%のカバレッジでもバグを見逃す可能性がある理由、および賢明なターゲット。

記事を読む →
データベースとSQL

SQL における DELETE、TRUNCATE、DROP: 違い、ロールバック、およびそれぞれをいつ使用するか

DELETE、TRUNCATE、DROP について、テスト済み SQL を使用して説明します。それぞれの削除内容、ロールバック、トリガー、ID リセット、速度、PostgreSQL、MySQL、SQL Server、SQLite の違いについて説明します。

記事を読む →
データベースとSQL

SQL ビューの説明: 作成、使用、落とし穴の回避、例を示します

SQL ビューについて、実行したクエリとともに説明しました。ビューの作成、ライブ結果、列の非表示、パフォーマンス、更新可能なビューと具体化されたビュー、壊れた依存関係、ベスト プラクティスについて説明します。

記事を読む →
ソフトウェア開発

TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト

TypeScript インターフェイスと型、実際のコンパイラでチェック: 両方でできること、片方だけでできること、宣言のマージ、拡張と交差、および従うべき簡単なルール。

記事を読む →
ソフトウェア開発

Python *args と **kwargs の説明: 柔軟な関数と明確な例

Python の *args と **kwargs について、実行したコードで説明しました。パッキングとアンパッキング、パラメーターの順序、キーワードのみの引数と位置のみの引数、デコレーター、一般的なエラーです。

記事を読む →
ソフトウェア開発

Python Lambda 関数: 構文、実際の用途、および使用すべきでない場合

Python のラムダ関数について、実行したコードとともに説明します。構文、キーによるソート、マップとフィルター、クロージャー、遅延バインディング トラップ、通常の def がより適切な選択である場合。

記事を読む →
人工知能

AIエージェントとは何ですか?シンプルなエージェント ループを実行できる仕組み

AIエージェントとは何ですか?エージェント ループ、ツール、メモリ、ガードレール、実行した小さな Python ループ、実際のリスク、およびシンプルなチャットボットがより良い選択である場合についてのわかりやすいガイドです。

記事を読む →
DevOps

モニタリングと可観測性: ログ、メトリクス、トレースによる本当の違い

モニタリングと可観測性について説明: 既知および未知の障害、ログ、メトリクスとトレース、チェックアウト インシデントのウォークスルー、ゴールデン シグナル、OpenTelemetry、および便利なアラート。

記事を読む →
DevOps

Docker ボリュームとバインド マウント: コンテナ データを正しい方法で保持する

Docker ボリュームとバインド マウントについて説明します。コンテナ データが消える理由、名前付きボリューム、バインド マウント、tmpfs、Compose の例、バックアップ、アクセス許可、およびそれぞれをいつ使用するかについて説明します。

記事を読む →
テスト自動化

TDD と BDD: 主な違い、1 つの機能が両方の方法で構築され、それぞれをいつ使用するか

TDD と BDD は、両方の方法で構築された 1 つの割引機能で説明されています。実行したコードによるレッド-グリーン-リファクタリング、Given-When-Then シナリオ、ツール、トレードオフ、およびそれぞれをいつ使用するかです。

記事を読む →
ソフトウェア開発

Git mergeとrebaseの違い:例と使い分け

Git mergeとrebaseの違いを、実際のログ出力、比較表、rebaseの黄金律、安全なチームワークフローで解説します。

記事を読む →
ソフトウェア開発

Pythonのリスト内包表記:構文・例・使うべきでない場面

Pythonのリスト内包表記を解説:構文、フィルタ、if-else、ネストしたループ、実測した処理時間、そして通常のforループの方が良い場面。

記事を読む →
データベースとSQL

SQL JOINを例で解説(Inner・Left・Right・Full)

サンプルのテーブルとクエリでSQL JOINを順に解説。INNER、LEFT、RIGHT、FULL、CROSS、SELFに加え、よくある間違いとパフォーマンスのコツも紹介します。

記事を読む →