PythonのMockと@patchのメモ

Pythonのテストで使うMockpatchについて

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オブジェクトである。モックmcrequests.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する関数は信頼性のあるものに限ったほうがいいだろう。

以上です。

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