pytest-mockを使ってユニットテストを実施しよう

スポンサーリンク

はじめに

ユニットテストのプラクティスとして「1モジュール・1テストファイル」という原則があります。 この原則を維持しつつ、外部モジュールへの依存を排除してテストを行うには、インポートされた関数の「モック化」が必須です。

本記事では、pytestを用いて依存関数の戻り値をシミュレートし、テストの独立性を高める方法を具体的なコード例とともにまとめました。

今までpytestを使用したことない方はぜひ以下の記事も一読いただくとより理解が深まります。

導入

Pythonのインストールされた環境にてpipを用いることで簡単に導入ができます。

pip install pytest pytest-mock

今回のプロジェクトのフォルダ・ファイル構成は以下のようになっています。

/
┣src/
┃┣sample.py
┃┗utils.py
┗tests/
 ┣test_sample.py
 ┗__init__.py

pytestの実行にはrootにてpytestコマンドを実行することでテストが実行できます。

実行例

サンプルコード

商品価格と通貨、商品個数を指定すると日本円に計算して返却する関数を作成しました。

from src.utils import get_rate

"""モック用"""
"""商品を日本円に計算する
    Args:
        price (int): 商品金額
        currency (str): 通貨名
        count (int): 商品個数
    Returns:
        int: 日本円合計
"""
def caluculate_item(price, currency, count=1):
    total = 0
    for _ in range(count):
        total = total + get_rate(currency) * price

    return total

通貨の日本円レートを取得する関数です。今回はsample.pyのユニットテストのみ実施のため実装しません。

def get_rate(currency):
    pass

テストコード

日本円を返却する関数に対して正しい値が返却されるか確認します。今回は、ユニットテストの実施のため関係のない関数などはモック化することで任意の値を返却させます。

サンプルとして、戻り値を固定して返却する方法、戻り値を複数指定する方法、例外を発生させる方法を記載しています。

モックを使用するには、テスト関数の引数にmockerを指定します。mocker.patchにてモック化する関数と返り値を指定します。モック化する関数は実際にテストするモジュールの場所を指定します。

import pytest
import src.sample as Sample

# モック対象関数の戻り値を固定する
def test_caluculate_item_JPY(mocker):
    mocker.patch("src.sample.get_rate", return_value=1)
    result = Sample.caluculate_item(150, "JPY")

    assert result == 150 

# モック対象関数の戻り値を複数指定する
def test_caluculate_items_USD(mocker):
    mock = mocker.patch("src.sample.get_rate", side_effect=[145, 144, 145])
    result = Sample.caluculate_item(1, "USD", 3)

    assert result == 434
    assert mock.call_count == 3 # モック関数の呼び出し階数を検証

# モック対象関数から例外を発生させる
def test_caluculate_item_invalid_currency(mocker):
    mocker.patch("src.sample.get_rate", side_effect=ValueError("It's a currency that doesn't exist."))
    with pytest.raises(ValueError) as info:
        Sample.caluculate_item(150, "XXX")

    assert info.type is ValueError
    assert str(info.value) == "It's a currency that doesn't exist."

実行結果

> pytest
=================== 2 passed in 0.02s ===================
platform win32 -- Python 3.10.11, pytest-9.0.2, pluggy-1.6.0
rootdir: \
plugins: mock-3.15.1
collected 3 items                                                                                                                                                                                                          

tests\test_sample.py ...                                                                                                                                                                                            [100%] 

=================== 3 passed in 0.02s =================== 

まとめ

本記事では、pytest-mock を活用してユニットテストの独立性を高める方法を解説しました。

ユニットテストの理想である「1モジュール・1テストファイル」の原則を守るためには、外部依存を切り離すスキルが欠かせません。今回紹介した手法を使い分けることで、複雑な外部連携を含むロジックも、シンプルかつ確実にテストできるようになります。

適切なモック化は「保守性の高いコード」を作ることにほかなりません。ぜひ、モックを活用してよいコードを書き続けられる環境を作りましょう…!

コメント

タイトルとURLをコピーしました