フリーキーズ | 独学プログラミング

Pythonのunittest.mockで依存をモックしてテストを書く方法!初心者向けガイド

リンドくん

リンドくん

たなべ先生、テストを書く時に外部APIを呼ぶ処理があると、テストが遅くなったり失敗したりするんですけど...

たなべ

たなべ

それは「モック(Mock)」という技術で解決できるよ!
外部の依存関係を「偽物」に置き換えることで、テストを高速で安定的に実行できるようになるんだ。

プログラミングでテストを書いていると、必ず出会うのが「外部依存」の問題です。

外部API、データベース、ファイルシステムなどに依存するコードをテストしようとすると、以下のような問題が発生します。

  • テストが遅い(ネットワーク通信に時間がかかる)
  • テストが不安定(外部サービスの状態に左右される)
  • テストが実行できない(ネット環境が必要)

そこで登場するのが「モック(Mock)」という技術です。
Pythonではunittest.mockという標準ライブラリを使って、外部依存を「偽物」に置き換えることができます。

この記事では、初心者の方でも理解できるよう、unittest.mockの基本的な使い方を実例とともに解説していきます。

プログラミング学習でお悩みの方へ

HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。

✓ 質問し放題

✓ β版公開中(2025年内の特別割引)

HackATAの詳細を見る

モック(Mock)とは何か

リンドくん

リンドくん

「モック」って具体的には何をするものなんですか?

たなべ

たなべ

簡単に言うと、本物の代わりに使う偽物だね。
映画撮影で本物のダイヤモンドの代わりに偽物を使うのと同じようなものだよ。

モックの基本概念

モック(Mock)とは、テスト時に実際のオブジェクトや関数の代わりに使用する「偽物」のことです。

例えば、以下のような外部APIを呼ぶ関数があるとします。

import requests

def get_weather(city):
    """外部APIから天気情報を取得"""
    response = requests.get(f"https://api.weather.com/v1/{city}")
    if response.status_code == 200:
        return response.json()['temperature']
    return None

def check_weather_alert(city):
    """天気をチェックして警報を出す"""
    temp = get_weather(city)
    if temp is None:
        return "天気情報が取得できません"
    elif temp > 35:
        return "猛暑日警報"
    elif temp < 0:
        return "氷点下警報"
    else:
        return "正常"

このcheck_weather_alert関数をテストしたい場合、毎回実際のAPIを呼び出すのは現実的ではありません。そこでモックを使います。

モックを使うメリット

  • テストが高速 - ネットワーク通信なしで瞬時に完了
  • テストが安定 - 外部サービスの状態に左右されない
  • 環境に依存しない - オフラインでもテスト実行可能
  • エラーケースのテストが簡単 - 意図的にエラーを発生させられる

unittest.mockの基本的な使い方

Mockオブジェクトの作成

まずは最も基本的な使い方から見てみましょう。

from unittest.mock import Mock

# 基本的なモックオブジェクト
mock_obj = Mock()

# 戻り値を設定
mock_obj.some_method.return_value = "Hello, Mock!"
print(mock_obj.some_method())  # "Hello, Mock!"

# 呼び出し回数を確認
mock_obj.some_method()
print(mock_obj.some_method.call_count)  # 2

@patchデコレータの使用

実際の関数をモックに置き換えるには@patchデコレータを使います。

import unittest
from unittest.mock import patch, Mock

class TestWeatherAlert(unittest.TestCase):
    
    @patch('__main__.get_weather')  # get_weather関数をモックで置き換え
    def test_hot_weather_alert(self, mock_get_weather):
        # モックの戻り値を設定(猛暑日)
        mock_get_weather.return_value = 36
        
        # テスト実行
        result = check_weather_alert("Tokyo")
        
        # 結果を確認
        self.assertEqual(result, "猛暑日警報")
        
        # モックが正しく呼ばれたか確認
        mock_get_weather.assert_called_once_with("Tokyo")
    
    @patch('__main__.get_weather')
    def test_cold_weather_alert(self, mock_get_weather):
        # 氷点下の場合
        mock_get_weather.return_value = -5
        
        result = check_weather_alert("Sapporo")
        
        self.assertEqual(result, "氷点下警報")
    
    @patch('__main__.get_weather')
    def test_api_error(self, mock_get_weather):
        # APIエラーの場合
        mock_get_weather.return_value = None
        
        result = check_weather_alert("Unknown")
        
        self.assertEqual(result, "天気情報が取得できません")

if __name__ == '__main__':
    unittest.main()

外部APIのモック実例

より実践的な例として、外部APIを使うユーザー管理システムのテストを見てみましょう。

import requests

class UserService:
    def __init__(self, api_base_url):
        self.api_base_url = api_base_url
    
    def get_user_info(self, user_id):
        """外部APIからユーザー情報を取得"""
        try:
            response = requests.get(f"{self.api_base_url}/users/{user_id}")
            if response.status_code == 200:
                return response.json()
            return None
        except requests.exceptions.RequestException:
            return None
    
    def is_premium_user(self, user_id):
        """ユーザーがプレミアム会員かチェック"""
        user_info = self.get_user_info(user_id)
        if user_info is None:
            return False
        return user_info.get('subscription_type') == 'premium'

このクラスのテストコードは以下のようになります。

import unittest
from unittest.mock import patch, Mock

class TestUserService(unittest.TestCase):
    
    def setUp(self):
        self.user_service = UserService("https://api.example.com")
    
    @patch('requests.get')
    def test_premium_user_check_success(self, mock_get):
        # モックレスポンスを作成
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'user_id': 123,
            'name': 'Test User',
            'subscription_type': 'premium'
        }
        mock_get.return_value = mock_response
        
        # テスト実行
        result = self.user_service.is_premium_user(123)
        
        # 結果確認
        self.assertTrue(result)
        
        # 正しいURLで呼ばれたか確認
        mock_get.assert_called_once_with("https://api.example.com/users/123")
    
    @patch('requests.get')
    def test_non_premium_user(self, mock_get):
        # 一般ユーザーの場合
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'user_id': 456,
            'name': 'Normal User',
            'subscription_type': 'free'
        }
        mock_get.return_value = mock_response
        
        result = self.user_service.is_premium_user(456)
        
        self.assertFalse(result)
    
    @patch('requests.get')
    def test_api_error(self, mock_get):
        # APIエラーの場合
        mock_get.side_effect = requests.exceptions.RequestException("Network error")
        
        result = self.user_service.is_premium_user(789)
        
        self.assertFalse(result)

よく使うモックのパターン

複数の戻り値を設定

from unittest.mock import Mock

mock_func = Mock()
# 呼び出すたびに異なる値を返す
mock_func.side_effect = [1, 2, 3]

print(mock_func())  # 1
print(mock_func())  # 2
print(mock_func())  # 3

例外を発生させる

mock_func = Mock()
# 例外を発生させる
mock_func.side_effect = ValueError("Something went wrong")

try:
    mock_func()
except ValueError as e:
    print(f"エラー: {e}")

呼び出し引数の確認

mock_func = Mock()
mock_func("test", key="value")

# 呼び出し引数を確認
print(mock_func.call_args)  # call('test', key='value')
print(mock_func.called)     # True
print(mock_func.call_count) # 1

注意点とベストプラクティス

リンドくん

リンドくん

モックを使う時に気をつけることはありますか?

たなべ

たなべ

そうだね、使いすぎに注意が一番大切かな。
外部依存だけをモックして、自分のロジックはしっかりテストするのがポイントだよ。

重要な原則

1. 外部依存のみをモックする

# 良い例:外部APIのみモック
@patch('requests.get')
def test_api_integration(self, mock_get):
    pass

# 悪い例:自分のロジックまでモック
@patch('my_module.calculate_score')  # これは意味がない
def test_user_ranking(self, mock_calc):
    pass

2. 実際のレスポンス形式に合わせる

# 実際のAPIレスポンスに近い形で設定
mock_response.json.return_value = {
    'status': 'success',
    'data': {
        'user_id': 123,
        'name': 'Test User'
    }
}

3. モックの呼び出しを検証する

# モックが正しいパラメータで呼ばれたかチェック
mock_api.assert_called_once_with('expected_param')

よくある間違い

間違い1 モックの設定し忘れ

@patch('requests.get')
def test_api_call(self, mock_get):
    # return_valueを設定し忘れると、Mockオブジェクトが返される
    result = fetch_data()  # Mockオブジェクトが返される
    # エラーになる!

間違い2 パッチ対象の指定ミス

# 間違い
@patch('requests.get')  # 元のモジュールを指定

# 正しい
@patch('my_module.requests.get')  # インポートした場所を指定

まとめ

リンドくん

リンドくん

モックの使い方がよく分かりました!さっそく自分のコードでも試してみます。

たなべ

たなべ

それは良いね!最初は外部API呼び出しを一つモックするところから始めてみて。
慣れてきたら徐々に複雑なケースにも挑戦していこう。

この記事では、Pythonのunittest.mockを使った外部依存のモック方法について解説しました。

重要なポイント

  • モックは外部依存を「偽物」に置き換える技術
  • テストが高速で安定になる
  • @patchデコレータで簡単に適用できる
  • 外部依存のみをモックし、自分のロジックはしっかりテストする

モックを使ったテストは、最初は複雑に感じるかもしれませんが、慣れてくると開発効率が劇的に向上します。

ぜひ今日から、自分のプロジェクトでunittest.mockを使ったテストを書いてみてください。
外部サービスに依存しない安定したテストで、より安心してコードを書けるようになるはずです!

この記事をシェア

関連するコンテンツ