0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python の unittest で Mock を使用しテストの幅を広げる

Posted at

はじめに

ユニットテストにおいて、外部依存性(データベース、API、ファイルシステムなど)の分離は重要な課題です。
Python の unittest.mock は、このような依存性を効果的にモック化するための強力なツールを提供します。

この記事では、外部 API を呼び出すクラスのテストを例に、unittest.mock の実践的な使い方を詳しく解説します。

テストターゲット

次のような処理でユーザー情報を取得するクラスのテストを実装することを考えます。

import requests

class UserService:
    def get_user_data(self, user_id):
        response = requests.get(f"https://example.com/users/{user_id}")
        return response.json()

※ 以前紹介した Requests ライブラリを使用してみました

テストの課題

対象の API が叩き放題であれば、おそらく下記の記事に沿ってテストを実装すれば実現は可能です。

しかし通常は下記のような課題があります。

  • テストの実施にネットワーク接続が必要になる
  • API の応答が遅いため、テストに時間がかかる
  • API によっては、API コール時に課金される可能性がある
  • テストの安定性と再現性の低下が懸念される

API の呼び出し部分はモック化し実際には呼び出さないようにして、API の仕様に沿ってダミーのレスポンスを返すようにできないでしょうか ?

モックテスト

unittest.Mock を使用することで、API を呼び出している requests.get の振舞いを Mock に置き換えることができます。
下記のコードをご確認ください。

import unittest
from unittest.mock import Mock, patch

class TestUserService(unittest.TestCase):
    @patch("requests.get")
    def test_get_user_data(self, mock_get):
        # モックのレスポンスを設定
        mock_response = Mock()
        mock_response.json.return_value = {
            "id": 1,
            "name": "Test User",
            "email": "test@example.com",
        }
        mock_get.return_value = mock_response

        # テスト実行
        service = UserService()
        result = service.get_user_data(1)

        # 検証
        self.assertEqual(result["name"], "Test User")

        # APIコール検証
        mock_get.assert_called_once_with("https://example.com/users/1")

@patch デコレータの詳細

@patch('requests.get') は、テストメソッド内で requests.get メソッドをモックに置き換えます。

主な特徴:

  • テストスコープ内でのみモックが有効
  • テストメソッドの第 2 引数としてモックオブジェクトが渡される
  • テストメソッド終了後に自動的に元の関数に戻る

return_value の活用

置き換えたメソッドの振舞いは、test_get_user_data メソッドの第 2 引数 mock_get を変更することでエミュレートできます。
今回の場合、return_value にダミーの値を設定することで、get メソッドの戻り値をテスト用のものに加工しています。

エラーケースのテスト

エラーを擬似的に発生させるには、Mock に side_effect を設定します。
下記のように記載することで、requests.get の実行時に例外を発生させられます。

@patch("requests.get")
def test_api_error_handling(self, mock_get):
    # HTTPエラーをシミュレート
    mock_get.side_effect = requests.exceptions.HTTPError("Not Found")

    service = UserService()

    with self.assertRaises(requests.exceptions.HTTPError):
        service.get_user_data(999)

まとめ

unittest.mock を使用することで、下記が実現できます。

  • 外部依存性からテストを分離
  • 高速で安定したユニットテストの実装

今回は一部の例のみをご紹介しましたが、入門サイトを見ると様々なケースへの対応方法が記載されていますので、一度確認してみてはいかがでしょうか ?

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?