こんにちは、コウキです。
mockとは、関数やクラスの機能を置き換えるもののことです。
テストコードを書くときによく使います。
テストのときだけ、この関数は常に1を返すようにしよう!
みたいなことができるようになります。
こうすれば、他の関数の影響を受けずに関数をテストする事ができます。
mockでできること
mockを使うとこんな事ができます。
- 関数の戻り値を変える
- 関数から例外を投げる
- 関数が呼ばれた回数をカウントする
- 関数が受け取った引数を記憶する
mockのインポート
mockは最初から入っているので、importするだけで使えます。
from unittest import mock
mockオブジェクトの作成
mockオブジェクトを作成する場合は、with文を使ったり、デコレータを使うのが一般的です。
今回はこの2つのやり方のみ扱います。
ちなみに、mockに置き換えられた関数は、何も設定しなければmockオブジェクトを返します。
with文を使った場合
with文を使うとその中だけ関数がmockに置き換わります。
import os
from unittest import mock
with mock.patch('os.path.join') as join_mock:
os.path.join('dir','filename') # mockオブジェクトを返す
デコレータを使った場合
デコレータを使った場合は、関数の中だけmockに置き換わります。
import os
from unittest import mock
@mock.patch('os.path.join')
def function(join_mock):
os.path.join('dir','filename') # mockオブジェクトを返す
モジュールの指定方法
さっきは'os.path.join'
としましたが、ここの指定方法は慣れるまで結構厄介です。
patchの引数に渡す文字列は以下のことに気をつけてください。
- importできる形になっている
- クラス名、メソッド名、関数名になっている
- 置き換えたい関数が含まれているモジュールを指定する
最後の部分が少しわかりにくいですね。。
例えば、以下のfunction.pyの中のfunc
という関数をテストしたい場合、os.path.join
とcalc
をmockにする必要があります。
# function.py
import os
def func():
os.path.join('dir','filename')
return 5 * calc(2, 3)
def calc(a, b):
return a * (a + b)
この場合はこのように指定してあげます。
# test_function.py
from unittest import mock
@mock.patch('function.os.path.join') # 2番目
@mock.patch('function.calc') # 1番目
def test_func(calc_mock, join_mock): # 1番目, 2番目
func()
このように、mockにしてあげるときはfunctionの中の関数を指定します。os.path.join
とした場合は、function.pyの中のos.path.joinはmockになりません。
それと、2つ以上の場合は、patchの順番と引数の順番には注意しましょう。
クラスの場合
クラスの場合も同じようにできます。
# function.py
import os
class MyClass:
def func():
os.path.join('dir','filename')
return 5 * calc(2, 3)
def calc(a, b):
return a * (a + b)
この場合はこのように指定してあげます。
# test_function.py
from function import MyClass
from unittest import mock
@mock.patch('function.os.path.join') # 2番目
@mock.patch('function.MyClass.calc') # 1番目
def test_func(calc_mock, join_mock): # 1番目, 2番目
MyClass().func()
ちなみにクラスまるごとmockに置き換える事もできます。
mockの戻り値を指定する
mockの戻り値の設定には色々な種類があります。
決められた値を返す
mockの戻り値を指定するにはreturn_value
に値を設定します。
これは、mockの戻り値を10に設定する例です。
mock_func.return_value = 10
もしくはpatchの引数として渡してやります。
@mock.patch('function.func', return_value=10)
呼ばれた回数で返す値を変える
この場合はside_effect
を使います。
1回目は10、2回目は20、3回目は30を返すようになります。
mock_func.side_effect = [10, 20, 30]
テストする関数の中で複数回おなじ関数を使っているときは、このやり方で返す値を変えると良いです。
条件によって返す値を変える
条件によって返す値を返すにはside_effect
に関数を入れます。
この例では、mockの引数に渡された値が+ならTrue、-ならFalseを返すようになります。
def side_effect_func(value):
if value >= 0:
return True
else:
return False
mock_func.side_effect = side_effect_func
このように受け取った引数によって返す値を変えたいときに使います。
関数の戻り値が別のクラスになってしまう場合に戻り値を設定する方法
例えばこんな場合にはどうやってmockにすればよいか悩んでしまいますね。
# function.py
import obj
def func():
obj1 = obj.func1('aaa')
obj2 = obj1.func2('bbb')
この場合は一旦objだけmockにします。
そして、関数をたどって変更したい関数のところで戻り値を設定します。
この例ではfunc2の戻り値を10に設定しています。
@mock.patch('function.obj')
def test_func(mock_obj):
mock.func1.return_value.func2.return_value =10
ちょっと長くてわかりにくいですが、一つずつ追っていけばできると思います。
mockから例外を投げる
mockから例外を投げるようにもできます。
この場合はside_effect
に例外クラスを渡すだけです。
mock_func.side_effect = Exception('message')
これを使えば、例外発生時のテストも簡単にできます。
mockが呼ばれた回数を調べる
mockには、呼ばれた回数を調べる機能もあります。
これは、mockを設定して、テストしたい関数を実行したあとに、期待した動作をしたかどうかを調べるために使います。
呼ばれた回数を取得する
あらかじめ呼ばれる回数がわかっているなら、これでテストすることができます。
mock_func.call_count
1回以上呼ばれたことをアサートする
これは少なくとも一回以上mockが呼ばれていたらテストをパスします。
mock_func.assert_called()
1回だけ呼ばれたことをアサートする
これは1回だけ呼ばれた場合にテストをパスします。
mock_func.assert_called()
1回だけ呼ばれたことをアサートする
これは1回だけ呼ばれた場合にテストをパスします。
mock_func.assert_called()
1回も呼ばれなかったことをアサートする
これは1回も呼ばれなかった場合にテストをパスします。
mock_func.assert_not_called()
mockに渡された引数を調べる
最後に呼ばれた引数をアサートする
最後に呼ばれた引数が期待した引数と一致した場合にテストをパスします。
この場合は、モック化した関数が最後に引数(1, 2, 3, test='aaa')
で呼ばれていたらテストをパスします。
mock_func.assert_called_with(1, 2, 3, test='aaa')
指定した引数で一度だけ呼ばれたかをアサートする
これは期待した引数で一度だけ呼ばれたかどうかをテストするためのものです。
mock_func.assert_called_once_with(1, 2, 3, test='aaa')
指定した引数で呼ばれたかをアサートする
これは、一度でも指定した引数で呼ばれていればテストをパスします。
mock_func.assert_any_call(1, 2, 3, test='aaa')
期待した引数がすべて呼び出されたかをアサートする
これは、複数回関数が呼び出されたときに、そのすべての引数が期待したものかどうかをテストできます。
これを使うときはcallをインポートしておきます。
from unittest.mock import call
calls = [call(1, test='aaa'), call(2, test='aaa')]
mock_func.assert_any_call(calls)
mock_func.assert_any_call(calls, any_order=True)
callsに呼び出されたすべての引数がなくてもテストはパスします。
逆にcallsに含まれている引数で関数が呼び出されていなければテストはパスしません。
any_orderをTrueにすると、順番が関係なくなります。
Falseにすると、呼び出された順番も一致していないといけません。
さらに、間で別の引数で呼ばれていた場合もテストはパスしません。
mockが呼び出されたときの引数をすべて取得する
引数をすべて取得したい場合はこれを使います。
mock_func.call_args_list
これはcallオブジェクトのリストで値を返します。
なので、(1, test='aaa')
と(2, test='aaa')
で呼ばれていた場合はこのような値が返ってきます。
[call(1, test='aaa'), call(2, test='aaa')]
mockの呼び出し情報をクリアする
呼び出し情報をクリアする場合はこのメソッドを使います。
mock_func.reset_mock()
最後に
今回はmockについてまとめてみました。
- 関数の戻り値を変える
- 関数から例外を投げる
- 関数が呼ばれた回数をカウントする
- 関数が受け取った引数を記憶する
いろいろな使い方があってややこしいですが、どんなことができるかを覚えておくだけでも、後で調べるときに役に立つと思います。
ぜひ参考にしてみてください^^
コメント