Python
のテストで使うMock
とpatch
について
mock
詳しいことは、unittest.mock --- 入門 — Python 3.8.2 ドキュメントにて。
mock
はテスト中で呼び出す必要はあるが、本来の動作はさせないで、呼び出しの記録(検証)などは行いたいときに用いるもの。
関数、オブジェクト、クラスなどなんにでも置き換えることができる。
requests.get
などをmock
で置き換えることはままあることだと思う。
今回はその利用例をメモとして残しておきたいのでまとめる。ちなみにMock
でなくMagicMock
を使うのが普通。
また、基本的にpatch
デコレータを使っている。
簡単な使い方
requests.get
を置き換える例。
some_module
内のsome_function
のなかで、requests.get
を利用している。これは割と一般的だと思う。
また、import requests
で行っていれば、some_module
外のrequests.get
を行うことができる。
詳しくは、unittest.mock --- モックオブジェクトライブラリ — Python 3.8.2 ドキュメントのwhere to patch
を参照のこと。
import some_module
from unittest.mock import patch, MagicMock
@patch('requests.get')
def test_some_function(mock_func): #1
mc = MagicMock()
mock_func.return_value = mc #2
with open('some_fixture_file', 'r') as f:
mc.text = f.read() #3
t = some_module.some_function()
mock_func.assert_called_once() #4
assert t == 'expected value'
mock
の柔らかさはすごい。粘土のようだ。以下はやっていることのメモ。
テスト用関数の引数mock_func
は、@patch
によって、渡されるmock
。(#1)
requests.get
の戻り値は、response
オブジェクトである。モックmc
をrequests.get
の戻り値として設定する。(#2)
some_function
では、このうち、text
プロパティを利用しているので、
そこにテスト用のテキストファイルの中身を仕掛ける。(#3)
普通にsome_function
をコールして、内部で、モックした関数requests.get
が呼び出されていることを確認する。(#4)
このような感じで使える。呼び出されたときの引数もチェックできるし、戻り値もside_effect
などをいじることで動的に変更もできる。
patchは多重も可能
patch
は多重にできる。内側が引数の手前にくる。
@patch('a.b')
@patch('requests.post')
@patch('some_module.some_function')
def test_post_today_n225(mf_some_func, mf_post, mf_b):
# ...
非常に便利だが、あまり時間はかけすぎず、シンプルに作っていくべきだ。
また、テストが十分にできていない関数などのpatch
は思わぬバグを引き起こす可能性があるので、patch
する関数は信頼性のあるものに限ったほうがいいだろう。
以上です。