【Python】unittestとmockの使い方を解説!

【Python】unittestとmockの使い方を解説!

記事の文字数:2022

Pythonでのソフトウェア開発において、単体テスト(unittest)は品質を確保するために不可欠な要素です。本記事では、Python標準ライブラリのunittestを使ったテストの基本から、unittest.mockを活用して外部依存を排除する方法 まで、実践的な実装方法を解説します。


スポンサーリンク

Pythonには、標準ライブラリとしてunittestというテストフレームワークが用意されています。また、unittest.mockモジュールを使うことで、外部依存を排除した単体テストが容易に作成できます。本記事では、unittestmockの基本的な使い方や単体テストコードの実行方法を解説します。

1. unittestの基本

unittestはPythonの標準テストフレームワークであり、テストケースの作成、実行、アサーションをサポートしています。

テストケースの作成方法

テストコードは以下のtest_add(self)のようにtest_から始まる関数名で作成します。

unittest-ex01.py
import unittest
# テスト対象の関数
def add(a, b):
return a + b
class TestMathOperations(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(0, 0), 0)
if __name__ == "__main__":
unittest.main()

assertEqualでaddの結果と期待値が同一であることを確認しています。

テストケースの実行方法

作成したテストコードを実行するには、以下のコマンドを使用します。

実行コマンド
python -m unittest -v unittest-ex01

実行結果は、以下のように表示されます。

実行結果
test_add (test.TestMathOperations.test_add) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK

特定のクラス・メソッドを実行

特定のテストクラスやテストメソッドを実行したい場合は、以下のように指定します。

Terminal window
python -m unittest -v unittest-ex01.TestMathOperations
python -m unittest -v unittest-ex01.TestMathOperations.test_add

2. unittest.mockの使い方

unittest.mockを使うと、関数やクラスをモック(偽物のオブジェクト)に置き換えてテストを実施できます。 mock.patch は、特定のモジュールやクラスのメソッドを一時的にモックに置き換えるためのデコレーターまたはコンテキストマネージャーです。

モックには例えば以下のような設定が可能です。

  • return_value: モックが呼ばれた際に返す値を指定
  • side_effect: 呼び出しごとに異なる動作を指定(関数を渡すことも可能)

外部APIの呼び出しをモックする

以下に実装例を示します。

unittest-ex02.py
from unittest import TestCase
from unittest.mock import patch
import requests
# テスト対象の関数
def fetch_data(url):
response = requests.get(url)
return response.json()
class TestFetchData(TestCase):
@patch("requests.get")
def test_fetch_data(self, mock_get):
mock_get.return_value.json.return_value = {"message": "Hello, world!"}
result = fetch_data("http://example.com")
self.assertEqual(result, {"message": "Hello, world!"})

@patchでrequests.getをモック化し、戻り値(return_value)に{"message": "Hello, world!"}を指定しています。
※実行にはrequestsモジュールが必要です。

デコレーターとしての使用

以下のように@patch.objectを指定し、デコレーター内にreturn_valueを指定することも可能です。

unittest-ex04.py
import unittest
from unittest.mock import patch
class MyClass:
def method(self):
return "real value"
class TestMyClass(unittest.TestCase):
@patch.object(MyClass, "method", return_value="mocked value")
def test_method(self, mock_method):
obj = MyClass()
self.assertEqual(obj.method(), "mocked value")
if __name__ == "__main__":
unittest.main()

@patch.objectデコレーター内でMyClassのmethodの戻り値をmocked valueに指定し、assertEqualmocked valueの期待値比較を行います。

コンテキストマネージャーとしての使用

withブロックでmockを指定することもできます。以下に実装例を示します。

unittest-ex05.py
import unittest
from unittest.mock import patch
import requests
class TestMyClass(unittest.TestCase):
def test_method(self):
with patch("requests.get") as mock_get:
mock_get.return_value.json.return_value = {"status": "ok"}
response = requests.get("http://example.com")
assert response.json() == {"status": "ok"}
if __name__ == "__main__":
unittest.main()

requests.getの戻り値のjsonの戻り値に{"status": "ok"}を指定、assertで取得した値を比較するテストコードです。

クラスのフィールドをモックする

クラスのインスタンス変数(フィールド)をモックすることで、特定の状態をシミュレートできます。

unittest-ex08.py
import unittest
from unittest.mock import patch
class Config:
def __init__(self):
self.api_key = "real_api_key"
class Service:
def __init__(self, config):
self.config = config
def get_api_key(self):
return self.config.api_key
class TestService(unittest.TestCase):
def test_get_api_key(self):
config = Config()
with patch.object(config, "api_key", new="mocked_api_key"): # インスタンス変数をモック化
service = Service(config)
self.assertEqual(service.get_api_key(), "mocked_api_key")
if __name__ == "__main__":
unittest.main()

上記patch.objectでConfingクラスのapi_keymocked_api_keyでモック化しています。

呼び出しごとに異なる値を返す

side_effectを利用すると呼び出しごとに異なる結果を出力することができます。

from unittest import TestCase
from unittest.mock import patch
def fetch_data():
import requests
response = requests.get("http://example.com")
return response.json()
class TestFetchData(TestCase):
@patch("requests.get")
def test_fetch_data_multiple_values(self, mock_get):
mock_get.return_value.json.side_effect = [
{"status": "ok"},
{"status": "error"}
]
self.assertEqual(fetch_data(), {"status": "ok"}) # 1回目の呼び出し
self.assertEqual(fetch_data(), {"status": "error"}) # 2回目の呼び出し

上記例ではfetch_data()を1回目と2回目で異なるレスポンスを返す挙動になります。

例外を発生させる

side_effectを利用することで、特定の例外を発生させることもできます。

from unittest import TestCase
from unittest.mock import patch
def fetch_data():
import requests
response = requests.get("http://example.com")
return response.json()
class TestFetchData(TestCase):
@patch("requests.get")
def test_fetch_data_raises_exception(self, mock_get):
mock_get.side_effect = Exception("Network error")
with self.assertRaises(Exception) as context:
fetch_data()
self.assertEqual(str(context.exception), "Network error")

上記例ではfetch_dataの結果にNetwork errorを発生させています。

まとめ

  • unittestを使って基本的なテストケースを作成できる。
  • unittest.mockを利用すると、外部依存を排除したテストが可能になる。
  • patchを活用することで、特定の関数やメソッドをモックできる。
  • クラスのフィールドをpatch.objectでモックすることで、環境設定や状態を変更可能。
  • side_effectを利用すると、呼び出しごとに異なる値を返したり、例外を発生させることができて、より柔軟なテストが可能になる。
  • return_valueを利用すると、指定した固定値を返すことができる。

unittestmockを活用して、効率的なテストを実装しましょう!

参考

unittest --- ユニットテストフレームワーク
unittest.mock --- モックオブジェクトライブラリ


以上で本記事の解説を終わります。
よいITライフを!
スポンサーリンク
Scroll to Top