📝

Python Mockを使用したテストの書き方

2024/06/18に公開

モック技術は、単体テストや統合テストを行う際に非常に有用です。実際の依存関係を模倣し、予測可能で制御可能な環境でテストを行うことができます。この記事では、モック技術を使用したテストの方法を解説し、具体的なサンプルコードを提供します。

目次

  1. モック技術とは
  2. モック技術の利点
  3. テストケースの概要
  4. モックを使ったテストの準備
  5. テストケースの解説
  6. サンプルコード

1. モック技術とは

モック技術は、テスト中に依存する外部リソース(例えば、データベース、外部API、ファイルシステムなど)を模倣する方法です。モックオブジェクトは、実際のオブジェクトの代わりに使用され、テストが予測可能で制御可能な結果を提供するようにします。

2. モック技術の利点

  • 制御可能: テスト環境を完全に制御することができます。
  • 高速化: 外部サービスやデータベースへのアクセスを避けることで、テストの実行速度を向上させます。
  • 再現性: 同じテストを何度も再現可能にします。

3. テストケースの概要

以下のテストケースでは、ECサイトのAPIを使用して商品を作成、更新、および取得する機能をテストしています。モック技術を使用して外部サービスへの依存を排除し、テストを実行可能にしています。

4. モックを使ったテストの準備

モック技術を使用するために、以下のライブラリを使用します。

  • pytest: Pythonのテストフレームワーク
  • pytest-asyncio: 非同期コードのテストをサポートするプラグイン
  • unittest.mock: モックオブジェクトを作成するための標準ライブラリ
  • mongomock_motor: 非同期MongoDBモッククライアント

5. テストケースの解説

テストケースでは、以下の手順でテストを行います。

  1. モックデータベースとモックサービスを準備する。
  2. モックを使ってテストを実行する。

6. サンプルコード

以下に、モック技術を使用したテストケースの完全なサンプルコードを示します。

サンプルコード

import pytest
import pytest_asyncio
from unittest.mock import patch, AsyncMock
from mongomock_motor import AsyncMongoMockClient
from fastapi.testclient import TestClient
from my_ec_site.main import app
from my_ec_site.schemas.product import ProductReqFactory
from my_ec_site.services.notification_service import NotificationService

client = TestClient(app)

# モックデータベースとモックサービスを準備するフィクスチャ
@pytest_asyncio.fixture()
async def prepare_mock_db():
    client = AsyncMongoMockClient()
    database = client["test_db"]
    await init_beanie_with_models(database)

@pytest_asyncio.fixture
def mock_notification_service():
    # NotificationServiceのモックを作成
    with patch("my_ec_site.services.notification_service.NotificationService") as mock_notification_service:
        mock_instance = mock_notification_service.return_value
        # send_notificationメソッドをモックして、常にTrueを返すように設定
        mock_instance.send_notification = AsyncMock(return_value=True)
        yield mock_instance

@pytest_asyncio.fixture
def override_dependency(mock_notification_service):
    # NotificationServiceの依存関係をモックに置き換える
    def _get_mock_service():
        return mock_notification_service
    app.dependency_overrides[NotificationService] = _get_mock_service
    yield
    # テスト終了後に依存関係の置き換えをクリアする
    app.dependency_overrides.clear()

# 商品の更新テスト
@pytest.mark.asyncio
async def test_update_product(prepare_mock_db, mocker):
    notification_service_mock = mocker.patch.object(NotificationService, "send_notification")
    req = ProductReqFactory.build()

    # 商品を作成
    create_response = client.post("/products", data=req.model_dump_json())
    created_product = create_response.json()

    # 更新用のリクエストを生成
    update_req = ProductReqFactory.build()
    update_req.status = "updated-status"

    # 商品を更新
    update_response = client.put(f"/products/{created_product['id']}", data=update_req.model_dump_json())

    # ステータスコードが200(OK)であることを確認
    assert update_response.status_code == 200
    updated_product = update_response.json()
    
    # 作成された商品と更新された商品のIDが一致することを確認
    assert created_product["id"] == updated_product["id"]
    
    # 更新されたステータスを確認
    assert updated_product["status"] == "updated-status"
    
    # 通知サービスが1回だけ呼び出されることを確認
    assert notification_service_mock.call_count == 1

# 商品の取得テスト
@pytest.mark.asyncio
async def test_get_product(override_dependency, prepare_mock_db):
    req = ProductReqFactory.build()

    # 商品を作成
    create_response = client.post("/products", data=req.model_dump_json())
    created_product = create_response.json()

    # 作成された商品をIDで取得
    response = client.get(f"/products/{created_product['id']}")
    assert response.status_code == 200
    fetched_product = response.json()
    
    # 取得した商品と作成された商品のIDが一致することを確認
    assert fetched_product["id"] == created_product["id"]

サンプルコードの解説

  • フィクスチャの準備:

    • prepare_mock_db: モックデータベースを初期化します。
    • mock_notification_service: NotificationServiceのモックを作成します。
    • override_dependency: NotificationServiceの依存関係をモックで置き換えます。
  • mock_notification_service:

    • patchを使用してNotificationServiceクラスをモック化します。
    • send_notificationメソッドをAsyncMockを使用してモックし、常にTrueを返すように設定します。これにより、通知サービスが実際に動作しなくてもテストを行うことができます。
  • override_dependency:

    • app.dependency_overridesを使用して、NotificationServiceの依存関係をモックに置き換えます。これにより、テスト中に実際のサービスではなくモックが使用されるようになります。
    • テストが終了したら、app.dependency_overrides.clear()を呼び出して、依存関係の置き換えをクリアします。
  • テストケース:

    • test_update_product: 商品の更新機能をテストします。NotificationServiceのメソッドが期待通りに呼び出されていることを確認します。
    • test_get_product: 商品の取得機能をテストします。作成した商品が正しく取得できることを確認します。

これらのテストケースは、外部依存をモックすることで、テストが予測可能で制御可能な環境で行えるようにしています。モック技術を使用することで、信頼性の高いテストを効率的に行うことができます。

Discussion