Pythonにて、「モックから別のモックを返す」テストコードを作成する機会がありました。
そこで、以下を参考に作成した時のメモを残します。
目次
環境
- Python 3.6.0
- unittest.mock.MagicMockを使用
- pytest 3.0.6
- テストランナーとして使用
状況
Targetクラスのrun()
メソッドについて、戻り値の辞書に正しく値が設定されているかを確認します。
target.py
from cook import Cook class Target: def run(self, material): # 引数materialは作るのに手間がかかるオブジェクト cook = Cook(material) cuisine = cook.bake() return { 'name': 'maindish', 'cuisine': cuisine.get_name(), }
Cookクラスのbake()
メソッドでは、さらに別のクラス(Ham, Spam, Egg)のインスタンスを返します。
cook.py
from cuisine import Ham, Spam, Egg class Cook: def __init__(self, material): self.__material = material def bake(self): try: # 実際には、materialの中身によって複雑な処理がある if material: return Ham() else: return Spam() except: # なかなかEggを出すデータを作れないとする return Egg()
Ham, Spam, Eggの各クラスは、それぞれget_name()
メソッドを持っています。
cuisine.py
class Ham: def get_name(self): return 'ham' class Spam: def get_name(self): return 'spam' class Egg: def get_name(self): return 'egg'
そんな中で、Target.run()の戻り値を検証しようとしましたが、
- get_name()で取得するオブジェクトは、run()の引数materialに依存する
- 引数material用のオブジェクトを生成するのは難しい
と、このままではテスト作成に手間がかかりそうでした。
対応
そこで今回は、unittest.mock.MagicMock
を使って2つモックを作ることで、引数material用のオブジェクトを生成しなくても済むようにします*1。
1つは、戻り値としてegg
を返すget_name()メソッドを持つモックです。
<MagicMockオブジェクト>.<差し替えたいメソッド名>.return_value
に、戻り値egg
をセットします。
from unittest.mock import MagicMock def test_bake(): # cuisineのモックを作る mock_cuisine = MagicMock() # get_name()メソッドは、'egg'を返すように指定 mock_cuisine.get_name.return_value = 'egg'
もう1つは、bake()メソッドを持ち、上記で作成したモックmock_cuisineを返すモックです。このモックはCookクラスと差し替えるのに使います。
import cook def test_bake(): ... # bake()メソッドでcuisineのモックを返す、モックを作る mock_cook = MagicMock() # bake()メソッドは、上記で作ったモック'mock_cuisine'を返すように指定 mock_cook.bake.return_value = mock_cuisine # Cookクラスはモックを返すモックに差し替える cook.Cook = mock_cook
準備ができたため、あとはテストコードを書きます。
from target import Target def test_bake(): ... sut = Target() # runの引数materialは、本来は作るのが面倒 # 今回はモック向けなので、何かあれば良い actual = sut.run('dummy') assert actual['cuisine'] == 'egg'
実行してみます。
$ python -m pytest ==== test session starts ==== platform darwin -- Python 3.6.0, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 rootdir: /Users/kamijoshinya/thinkami/try/python_mock_sample, inifile: pytest.ini collected 1 items test_get_mock_object.py . ==== 1 passed in 0.07 seconds ====
Target.run()メソッドの戻り値が差し替わり、テストがpassしました。
ソースコード
GitHubにあげました。e.g._get_mock_object
ディレクトリの中が今回のファイルです。
thinkAmi-sandbox/python_mock-sample
*1:他の解決方法があるかもしれませんが、今回はモックから別のモックを返すための例なので…