概要
以下のようにランダムな処理を含む関数やメソッドを単体テストしたいときに、unittest.mockのpatchが使えます。
import random
def roulette(call_number: int) -> int:
"""
1~10までの数字が書かれたルーレットで、数字を当てるゲームを表した関数
"""
if random.randint(1, 10) == call_number:
return 1000
else:
return 0
環境
- Python3.11
unittest.mockを使わない場合の課題
概要に記載したコードを例に進めます。
すなおに処理を書くと次のようになります。ただ、roulette()
内でrandom.randint()
を使っているため、roulette()
の引数として渡している数字と、random.randint()
で返される数字が同じになるか不確実です。
import unittest
import roulette
class TestRoulette(unittest.TestCase):
def test_equal_call_number(self):
self.assertEqual(roulette(5), 1000) # 5 == random.randint(1, 10)は不確実
def test_not_equal_call_number(self):
self.assertEqual(roulette(5), 0) # 5 != random.randint(1, 10)は不確実
unittest.mockのpatchを使う
unittest.mockのpatchはこのような場合に使えます。
import unittest
import roulette
class TestRoulette(unittest.TestCase):
@patch("random.randint")
def test_equal_call_number(self, mock_randint):
mock_randint.return_value = 5
self.assertEqual(roulette(5), 1000) # 5 == random.randint(1, 10)は確実
@patch("random.randint")
def test_not_equal_call_number(self, mock_randint):
mock_randint.return_value = 1
self.assertEqual(roulette(5), 0) # 5 != random.randint(1, 10)は確実
@patch("random.randint")
とすると、random.randint
のモック用インスタンスが返されます。これはmock_randint
引数に代入されます(引数名は自由)。
その後、モック用インスタンスのインスタンス変数return_value
へ戻り値にしたい値を入れます。
この戻り値はrandom.randint
の戻り値となります。
そのためmock_randint.return_value = 5
とすればrandom.randint(1, 10)
は必ず5
を返します。
このようにすることで、ランダムな処理を含んでいる場合でも単体テストを行えます。
参考
How can I test functions, which use random?
ランダムな処理を含む単体テストの例
patch
Python公式ドキュメントunittest.mockのpatch
Pythonのデコレータを理解するまで
デコレーターに関する詳しい説明