patch はコンテクストマネージャでもあるので、with を使った書き方ができます。with でつくられたブロックの中でだけ、モックが機能するようになります。
>>> from mock import patch >>> with patch('random.random') as m: ... import random ... random.random() # もうモックになっている。戻り値は Mock オブジェクト ... m.return_value = 0.5 ... random.random() # 戻り値は 0.5 ... <mock.Mock object at 0x423ed0> 0.5 >>> random.random() # モノホンの random 関数 0.84432022018162511
with ブロックの中に入った時点で、random.random はモックになってしまいます。必須ではありませんが、
as m
のように書いて、変数 m でモックへアクセスできます。patch().start() の戻り値と同じです。
with ブロックを抜けるときに、パッチャー の stop() メソッドが実行されたのと同じ状態になり、モックではなくなります。
複数のモックを使いたいときには、with ブロックをネストするわけですが、それはちょっと鬱陶しいですよね。インデントが深くなりすぎるかも、なので。そんな時は 標準ライブラリの contextlib.nested を使います。
>>> from contextlib import nested >>> from mock import patch >>> from __future__ import with_statement >>> with nested(patch('random.random'), patch('random.randint')) as (m, n): ... m.return_value = 0.5 ... n.return_value = 3 # random.randint のモック
patch は、デコレータとしても使えます。
>>> from mock import patch >>> @patch('random.random') ... def func(m): ... import random ... print random.random() ... m.return_value = 0.5 ... print random.random() ... >>> func() <mock.Mock object at 0x429730> 0.5 >>> random.random() 0.94254256426687633
関数を patch でデコレートすると、元の関数にMock オブジェクトが渡されるようになります。上の例では、第1引数 m が random.random に対応するモックです。このモックが使われているスコープ内、つまりこの関数内でモックが有効になっています。関数を抜けると(正確には、関数を抜けて、デコレートしている外側のスコープを抜けると)、モックではなくなります。したがって、 func() を呼び出した後で、random.random() を呼び出すと本来の戻り値になります。
unittest モジュールを使ったテストを書く時には、テスト関数に、デコレータからモックを受け取るように書きます。
class MyTest(TestCase): @patch('random.random') def test1(self, m): import random m.return_value = 10.0 self.assertEqual(random.random(), 10.0)
というわけで mock オブジェクトの機能でした。機能を知ることと、使えることはまた別なので、実際にテストでどうするのがいいか、は、別の機会に。私自身、試行錯誤しております。