mockはこう使え
最近、Mockライブラリ http://www.voidspace.org.uk/python/mock/ を使ってみたのでメモ。
このライブラリは、その性質上、動的にメソッドや属性を作成するケースが多く、普通のPythonライブラリのようにイントロスペクションに頼って使い方を調べるのは難しい。本気で使うならまじめにドキュメントを読み込む必要がある。
関数の置き換え
テスト中に呼び出される関数をMockで置き換える例。ここでは、関数 myapp.utils.func1()
を置き換える。
from mock import Mock import myapp.utils # myapp.utils.func1 を、常に100を返す関数に置き換える myapp.utils.func1 = Mock(return_value=100)
戻り値が定数でない場合は、Mock()
にside_effect
で戻り値を作成する関数を指定する。
def ret_value(a, b,c): return a+b+c import myapp.utils myapp.utils.func1 = Mock(side_effect=ret_value) myapp.utils.func1(100, 200, 300) # ret_value(100, 200, 300) が呼び出される
side_effect
に例外型を指定すると、呼び出し時に例外が発生する。
import myapp.utils myapp.utils.func1 = Mock(side_effect=ValueError) myapp.utils.func1(100, 200, 300) # ValueErrorが発生する
クラスの置き換え
class Spam: def __init__(self, ham): pass def egg(self): return 100
こんなクラスを置き換えるばあい、次のように書ける。
import myapp.utils myapp.utils.Spam = Mock() myapp.utils.Spam.return_value.egg.return_value = 100
こうも書ける。
myapp.utils.Spam = Mock(**{ 'return_value.egg.return_value':100, })
動的な置き換え
mock.patch()
を使って、一時的に置き換えて自動的に元に元通りに復元することが出来る。
patch()
をデコレータとして使用すれば、関数の実行中のみ置き換えられる。
from mock import patch def func(): return myapp.utils.spam() @patch('myapp.utils.spam', return_value=100) def testfunc(): ret = func() assert ret == 100
また、コンテキストマネージャとしても使える。
def func(): return myapp.utils.spam() def testfunc(): with patch('myapp.utils.spam', return_value=100) as m: ret = func() assert ret == 100
patch()
に渡す引数のうち、先頭の置き換え対象の指定以外は、Mock()
に渡される。
def func(): return myapp.utils.SpamClass().ham() def testfunc(): with patch('myapp.utils.SpamClass', **{return_value.ham.return_value:100} as m: ret = func() assert ret == 100
コンテキストマネージャでpatch()
を使う場合、複数のオブジェクトを同時に置き換える時に
def test(): with patch('testapp.func1') as m1: with patch('testapp.func2') as m2: with patch('testapp.func3') as m3: with patch('testapp.func4') as m4: func()
なんて書くのはみっともない。Python2.xなら、contextlib.nested()
を使ってこう書こう。
from contextlib import nested def test(): with nested( patch('testapp.func1'), patch('testapp.func2'), patch('testapp.func3'), patch('testapp.func4')) as (m1, m2, m3, m4): func()
呼び出し履歴の確認
Mock
オブジェクトの呼び出しは、Mock.call_count
等の属性値で確認できる。
from mock import * def test(*args, **kwargs): pass def f(*args, **kwargs): return test(*args, **kwargs) def func(): with patch('__main__.test', return_value=100) as m: print f(1,2,3,abc=100) print f(4,5,6,abc=200) print "call_count:", m.call_count print "call_args:", list(m.call_args) print "call_args_list:", m.call_args_list if __name__ == '__main__': func()
call_count: 2 call_args: [(4, 5, 6), {'abc': 200}] call_args_list: [call(1, 2, 3, abc=100), call(4, 5, 6, abc=200)]
call_args
やcall_args_list
は、mock
を呼び出したときの引数をcall
オブジェクトに保存して格納している。
call
オブジェクトから引数を取り出すときには、次のように記述する。
args, kwargs = m.call_args print "引数:%s キーワード引数:%s" % (args, kwargs)