mock ライブラリの patch 関数の挙動を書きます。
パッチャーの start()/stop() メソッドを使う
標準ライブラリの random.random 関数を例にとります。
>>> import random >>> random.random() 0.90675850364670885 >>> random.random() 0.9838226858480108
patch 関数を実行してみましょう。
>>> from mock import patch >>> p = patch('random.random') >>> random.random() 0.27552766919082217
とくに、何も変わったことは起こりません。patch 関数の戻り値は _patch オブジェクトです。ドキュメントにはパッチャーと書いてあります。パッチャーの start() メソッドを呼ぶと変化が起こります。
>>> m = p.start() >>> m <mock.Mock object at 0x366f30> >>> random.random <mock.Mock object at 0x366f30>
random.random は Mock オブジェクトなので戻り値を書き換えることもできます。
パッチャーには stop() メソッドがあり、これを実行すると元に戻ります。
>>> p.stop() >>> random.random <built-in method random of Random object at 0x6b3b6210> >>> random.random() 0.029689653478273681
テストで使う
def foo(x): return random.random() * x
関数 foo をテストする場合を考えます。テストすべきは、
- random.random を引数なしで 1 度呼び出したこと。
- random.random() の戻り値に、x をかけたものが返ること。
です。random 関数の戻り値がわかっていれば、テストできますね。モックしましょう。
import random import unittest import mock def foo(x): return random.random() * x class MyTestCase(unittest.TestCase): def test(self): # random.random が常に1を返すようモックる p = mock.patch('random.random') p.start() random.random.return_value = 1 # テスト対象関数を呼び出す result = foo(2) # random.random() を1度呼んでいることを確認 self.assertEqual(random.random.call_count, 1) self.assertEqual(random.random.call_args, ((), {})) # 戻り値をテスト self.assertEqual(result, 2) # モックを戻す p.stop() if __name__ == '__main__': unittest.main()
これでテストできるようになりました。ですが、問題がありまして、テスト中に例外が出ると p.stop() が呼ばれません。random.random はモックのままなので、他のテストが実行されたきに、意図しない結果になることもあります。
というわけで、必ず実行されるように、setUp と tearDown で、パッチャーの start() と stop() 使います。
class MyTestCase(unittest.TestCase): def setUp(self): # random.random が常に1を返すようモックる self.p = mock.patch('random.random') self.p.start() random.random.return_value = 1 def tearDown(self): # モックを戻す self.p.stop() def test(self): # テスト対象関数を呼び出す result = foo(2) # random.random() を1度呼んでいることを確認 self.assertEqual(random.random.call_count, 1) self.assertEqual(random.random.call_args, ((), {})) # 戻り値をテスト self.assertEqual(result, 2)
つづき
patch をデコレータや、コンテクストマネージャとして使えるのですが、それは、また今度。