unittest.mock —はじめに—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/library/unittest.mock-examples
移動先:案内検索

unittest.mock —はじめに

バージョン3.3の新機能。


モックの使用

模擬パッチ適用方法

Mock オブジェクトの一般的な使用法は次のとおりです。

  • パッチ適用方法
  • オブジェクトに対するメソッド呼び出しの記録

オブジェクトのメソッドを置き換えて、システムの別の部分によって正しい引数で呼び出されていることを確認することをお勧めします。

>>> real = SomeClass()
>>> real.method = MagicMock(name='method')
>>> real.method(3, 4, 5, key='value')
<MagicMock name='method()' id='...'>

モックが使用されると(この例ではreal.method)、モックがどのように使用されたかについてアサーションを作成できるメソッドと属性があります。

ノート

これらの例のほとんどでは、 Mock クラスと MagicMock クラスは交換可能です。 MagicMockはより高性能なクラスであるため、デフォルトで使用するのが賢明なクラスになります。


モックが呼び出されると、その呼び出された属性はTrueに設定されます。 さらに重要なことに、 assert_called_with()または assert_called_once_with()メソッドを使用して、正しい引数で呼び出されたことを確認できます。

この例では、ProductionClass().methodを呼び出すと、somethingメソッドが呼び出されることをテストします。

>>> class ProductionClass:
...     def method(self):
...         self.something(1, 2, 3)
...     def something(self, a, b, c):
...         pass
...
>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3)

オブジェクトのメソッド呼び出しのモック

最後の例では、オブジェクトに直接メソッドをパッチして、正しく呼び出されたことを確認しました。 もう1つの一般的な使用例は、オブジェクトをメソッド(またはテスト対象のシステムの一部)に渡し、それが正しい方法で使用されていることを確認することです。

以下の単純なProductionClassには、closerメソッドがあります。 オブジェクトで呼び出されると、closeが呼び出されます。

>>> class ProductionClass:
...     def closer(self, something):
...         something.close()
...

したがって、テストするには、closeメソッドを使用してオブジェクトを渡し、正しく呼び出されたことを確認する必要があります。

>>> real = ProductionClass()
>>> mock = Mock()
>>> real.closer(mock)
>>> mock.close.assert_called_with()

モックに「close」メソッドを提供するために、何もする必要はありません。 closeにアクセスすると作成されます。 したがって、「close」がまだ呼び出されていない場合、テストでそれにアクセスすると作成されますが、 assert_called_with()は失敗例外を発生させます。


モッククラス

一般的なユースケースは、テスト対象のコードによってインスタンス化されたクラスをモックアウトすることです。 クラスにパッチを適用すると、そのクラスはモックに置き換えられます。 インスタンスは、クラスを呼び出すことによって作成されます。 これは、モックされたクラスの戻り値を見て、「モックインスタンス」にアクセスすることを意味します。

以下の例では、Fooをインスタンス化してそのメソッドを呼び出す関数some_functionがあります。 patch()を呼び出すと、クラスFooがモックに置き換えられます。 Fooインスタンスはモックを呼び出した結果であるため、モック return_value を変更して構成されます。

>>> def some_function():
...     instance = module.Foo()
...     return instance.method()
...
>>> with patch('module.Foo') as mock:
...     instance = mock.return_value
...     instance.method.return_value = 'the result'
...     result = some_function()
...     assert result == 'the result'

モックに名前を付ける

モックに名前を付けると便利です。 名前はモックのreprに表示され、モックがテスト失敗メッセージに表示されるときに役立ちます。 名前は、モックの属性またはメソッドにも伝達されます。

>>> mock = MagicMock(name='foo')
>>> mock
<MagicMock name='foo' id='...'>
>>> mock.method
<MagicMock name='foo.method' id='...'>

すべての通話を追跡する

多くの場合、メソッドへの複数の呼び出しを追跡する必要があります。 mock_calls 属性は、モックの子属性へのすべての呼び出しと、その子へのすべての呼び出しを記録します。

>>> mock = MagicMock()
>>> mock.method()
<MagicMock name='mock.method()' id='...'>
>>> mock.attribute.method(10, x=53)
<MagicMock name='mock.attribute.method()' id='...'>
>>> mock.mock_calls
[call.method(), call.attribute.method(10, x=53)]

mock_callsについてアサーションを作成し、予期しないメソッドが呼び出された場合、アサーションは失敗します。 これは、予期した呼び出しが行われたことを表明するだけでなく、それらが正しい順序で行われ、追加の呼び出しがないことも確認しているため、便利です。

call オブジェクトを使用して、mock_callsと比較するためのリストを作成します。

>>> expected = [call.method(), call.attribute.method(10, x=53)]
>>> mock.mock_calls == expected
True

ただし、モックを返す呼び出しのパラメーターは記録されません。つまり、祖先の作成に使用されるパラメーターが重要なネストされた呼び出しを追跡することはできません。

>>> m = Mock()
>>> m.factory(important=True).deliver()
<Mock name='mock.factory().deliver()' id='...'>
>>> m.mock_calls[-1] == call.factory(important=False).deliver()
True

戻り値と属性の設定

モックオブジェクトに戻り値を設定するのは簡単です。

>>> mock = Mock()
>>> mock.return_value = 3
>>> mock()
3

もちろん、モックのメソッドについても同じことができます。

>>> mock = Mock()
>>> mock.method.return_value = 3
>>> mock.method()
3

戻り値はコンストラクターでも設定できます。

>>> mock = Mock(return_value=3)
>>> mock()
3

モックに属性設定が必要な場合は、次のようにします。

>>> mock = Mock()
>>> mock.x = 3
>>> mock.x
3

mock.connection.cursor().execute("SELECT 1")など、より複雑な状況をモックアップしたい場合があります。 この呼び出しでリストを返すようにする場合は、ネストされた呼び出しの結果を構成する必要があります。

call を使用して、後で簡単にアサーションできるように、次のような「連鎖呼び出し」で一連の呼び出しを作成できます。

>>> mock = Mock()
>>> cursor = mock.connection.cursor.return_value
>>> cursor.execute.return_value = ['foo']
>>> mock.connection.cursor().execute("SELECT 1")
['foo']
>>> expected = call.connection.cursor().execute("SELECT 1").call_list()
>>> mock.mock_calls
[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
>>> mock.mock_calls == expected
True

呼び出しオブジェクトを連鎖呼び出しを表す呼び出しのリストに変換するのは、.call_list()への呼び出しです。


モックで例外を発生させる

有用な属性は side_effect です。 これを例外クラスまたはインスタンスに設定すると、モックが呼び出されたときに例外が発生します。

>>> mock = Mock(side_effect=Exception('Boom!'))
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!

副作用関数と反復可能

side_effectは関数または反復可能に設定することもできます。 反復可能としてのside_effectのユースケースは、モックが数回呼び出され、各呼び出しが異なる値を返すようにする場合です。 side_effectをイテラブルに設定すると、モックを呼び出すたびにイテラブルから次の値が返されます。

>>> mock = MagicMock(side_effect=[4, 5, 6])
>>> mock()
4
>>> mock()
5
>>> mock()
6

モックが何で呼び出されるかに応じて戻り値を動的に変化させるなど、より高度なユースケースでは、side_effectを関数にすることができます。 関数は、モックと同じ引数で呼び出されます。 関数が返すものはすべて、呼び出しが返すものです。

>>> vals = {(1, 2): 1, (2, 3): 2}
>>> def side_effect(*args):
...     return vals[args]
...
>>> mock = MagicMock(side_effect=side_effect)
>>> mock(1, 2)
1
>>> mock(2, 3)
2

非同期イテレータのモック

Python 3.8以降、AsyncMockおよびMagicMockは、非同期イテレーターから__aiter__のモックをサポートしています。 __aiter__return_value 属性を使用して、反復に使用する戻り値を設定できます。

>>> mock = MagicMock()  # AsyncMock also works here
>>> mock.__aiter__.return_value = [1, 2, 3]
>>> async def main():
...     return [i async for i in mock]
...
>>> asyncio.run(main())
[1, 2, 3]

非同期コンテキストマネージャーのモック

Python 3.8以降、AsyncMockおよびMagicMockは、非同期コンテキストマネージャーから__aenter__および__aexit__のモックをサポートしています。 デフォルトでは、__aenter____aexit__は非同期関数を返すAsyncMockインスタンスです。

>>> class AsyncContextManager:
...     async def __aenter__(self):
...         return self
...     async def __aexit__(self, exc_type, exc, tb):
...         pass
...
>>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here
>>> async def main():
...     async with mock_instance as result:
...         pass
...
>>> asyncio.run(main())
>>> mock_instance.__aenter__.assert_awaited_once()
>>> mock_instance.__aexit__.assert_awaited_once()

既存のオブジェクトからモックを作成する

モックの使いすぎに関する問題の1つは、テストが実際のコードではなくモックの実装に結合されることです。 some_methodを実装するクラスがあるとします。 別のクラスのテストでは、some_methodを提供するこのオブジェクトのモックを提供します。 後で最初のクラスをリファクタリングして、some_methodがなくなると、コードが壊れていても、テストは引き続き合格になります。

Mock では、 spec キーワード引数を使用して、モックの仕様としてオブジェクトを提供できます。 仕様オブジェクトに存在しないモックのメソッド/属性にアクセスすると、すぐに属性エラーが発生します。 仕様の実装を変更すると、そのクラスを使用するテストは、それらのテストでクラスをインスタンス化する必要なしに、すぐに失敗し始めます。

>>> mock = Mock(spec=SomeClass)
>>> mock.old_method()
Traceback (most recent call last):
   ...
AttributeError: object has no attribute 'old_method'

仕様を使用すると、一部のパラメーターが定位置引数または名前付き引数として渡されたかどうかに関係なく、モックに対して行われた呼び出しのよりスマートなマッチングも可能になります。

>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, 3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(a=1, b=2, c=3)

このよりスマートなマッチングをモックのメソッド呼び出しでも機能させたい場合は、自動仕様を使用できます。

任意の属性の設定と取得を防ぐ、より強力な仕様が必要な場合は、 spec の代わりに spec_set を使用できます。


パッチデコレータ

ノート

patch()では、オブジェクトが検索される名前空間内のオブジェクトにパッチを適用することが重要です。 これは通常簡単ですが、クイックガイドについてはパッチを適用する場所をお読みください。


テストでよく必要なのは、クラス属性またはモジュール属性にパッチを適用することです。たとえば、組み込みのパッチを適用したり、モジュール内のクラスにパッチを適用して、インスタンス化されていることをテストしたりします。 モジュールとクラスは事実上グローバルであるため、テスト後にパッチを適用する必要があります。そうしないと、パッチが他のテストに残り、問題の診断が困難になります。

モックは、 patch()patch.object()patch.dict()の3つの便利なデコレータを提供します。 patchは、package.module.Class.attributeの形式の単一の文字列を取り、パッチを適用する属性を指定します。 また、オプションで、属性(またはクラスなど)を置き換える値を取ります。 'patch.object'は、パッチを適用するオブジェクトと属性の名前に加えて、オプションでパッチを適用する値を取ります。

patch.object

>>> original = SomeClass.attribute
>>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
... def test():
...     assert SomeClass.attribute == sentinel.attribute
...
>>> test()
>>> assert SomeClass.attribute == original

>>> @patch('package.module.attribute', sentinel.attribute)
... def test():
...     from package.module import attribute
...     assert attribute is sentinel.attribute
...
>>> test()

モジュール( builtins を含む)にパッチを適用する場合は、 patch.object()の代わりに patch()を使用してください。

>>> mock = MagicMock(return_value=sentinel.file_handle)
>>> with patch('builtins.open', mock):
...     handle = open('filename', 'r')
...
>>> mock.assert_called_with('filename', 'r')
>>> assert handle == sentinel.file_handle, "incorrect file handle returned"

モジュール名は、必要に応じてpackage.moduleの形式で「ドット」にすることができます。

>>> @patch('package.module.ClassName.attribute', sentinel.attribute)
... def test():
...     from package.module import ClassName
...     assert ClassName.attribute == sentinel.attribute
...
>>> test()

良いパターンは、実際にテストメソッド自体を装飾することです。

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'attribute', sentinel.attribute)
...     def test_something(self):
...         self.assertEqual(SomeClass.attribute, sentinel.attribute)
...
>>> original = SomeClass.attribute
>>> MyTest('test_something').test_something()
>>> assert SomeClass.attribute == original

モックでパッチを適用する場合は、 patch()を1つの引数のみで(または patch.object()を2つの引数で)使用できます。 モックが作成され、テスト関数/メソッドに渡されます。

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'static_method')
...     def test_something(self, mock_method):
...         SomeClass.static_method()
...         mock_method.assert_called_with()
...
>>> MyTest('test_something').test_something()

このパターンを使用して、複数のパッチデコレータを積み重ねることができます。

>>> class MyTest(unittest.TestCase):
...     @patch('package.module.ClassName1')
...     @patch('package.module.ClassName2')
...     def test_something(self, MockClass2, MockClass1):
...         self.assertIs(package.module.ClassName1, MockClass1)
...         self.assertIs(package.module.ClassName2, MockClass2)
...
>>> MyTest('test_something').test_something()

パッチデコレータをネストすると、モックは適用されたのと同じ順序でデコレートされた関数に渡されます(デコレータが適用される通常の Python の順序)。 これは下から上を意味するため、上記の例では、test_module.ClassName2のモックが最初に渡されます。

スコープ中にディクショナリに値を設定し、テストの終了時にディクショナリを元の状態に復元するための patch.dict()もあります。

>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original

patchpatch.objectpatch.dictはすべてコンテキストマネージャーとして使用できます。

patch()を使用してモックを作成する場合、withステートメントの「as」形式を使用してモックへの参照を取得できます。

>>> class ProductionClass:
...     def method(self):
...         pass
...
>>> with patch.object(ProductionClass, 'method') as mock_method:
...     mock_method.return_value = None
...     real = ProductionClass()
...     real.method(1, 2, 3)
...
>>> mock_method.assert_called_with(1, 2, 3)

別の方法として、patchpatch.objectおよびpatch.dictをクラスデコレータとして使用できます。 このように使用すると、名前が「test」で始まるすべてのメソッドにデコレータを個別に適用するのと同じです。


さらなる例

少し高度なシナリオの例をいくつか示します。

連鎖呼び出しのモック

return_value 属性を理解すれば、連鎖呼び出しのモックは実際にはモックで簡単です。 モックが初めて呼び出されたとき、または呼び出される前にreturn_valueをフェッチすると、新しいモックが作成されます。

これは、return_valueモックに問い合わせることで、モックオブジェクトの呼び出しから返されたオブジェクトがどのように使用されたかを確認できることを意味します。

>>> mock = Mock()
>>> mock().foo(a=2, b=3)
<Mock name='mock().foo()' id='...'>
>>> mock.return_value.foo.assert_called_with(a=2, b=3)

ここからは、連鎖呼び出しについて構成してアサーションを作成する簡単な手順です。 もちろん、別の方法は、そもそもよりテスト可能な方法でコードを書くことです…

したがって、次のようなコードがあるとします。

>>> class Something:
...     def __init__(self):
...         self.backend = BackendProvider()
...     def method(self):
...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
...         # more code

BackendProviderがすでに十分にテストされていると仮定すると、method()をどのようにテストしますか? 具体的には、コードセクション# more codeが応答オブジェクトを正しい方法で使用することをテストする必要があります。

この一連の呼び出しはインスタンス属性から行われるため、Somethingインスタンスのbackend属性にモンキーパッチを適用できます。 この特定のケースでは、start_callへの最後の呼び出しからの戻り値にのみ関心があるため、実行する構成はあまりありません。 返されるオブジェクトが「ファイルのような」ものであると仮定して、応答オブジェクトが組み込みの open()specとして使用するようにします。

これを行うには、モックバックエンドとしてモックインスタンスを作成し、そのモック応答オブジェクトを作成します。 その最終的なstart_callの戻り値として応答を設定するには、次のようにします。

mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response

configure_mock()メソッドを使用して、少し良い方法でこれを実行し、戻り値を直接設定できます。

>>> something = Something()
>>> mock_response = Mock(spec=open)
>>> mock_backend = Mock()
>>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
>>> mock_backend.configure_mock(**config)

これらを使用して、「モックバックエンド」にモンキーパッチを適用し、実際の呼び出しを行うことができます。

>>> something.backend = mock_backend
>>> something.method()

mock_calls を使用すると、単一のアサートで連鎖呼び出しを確認できます。 連鎖呼び出しは、1行のコードに複数の呼び出しがあるため、mock_callsには複数のエントリがあります。 call.call_list()を使用して、次の呼び出しリストを作成できます。

>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
>>> call_list = chained.call_list()
>>> assert mock_backend.mock_calls == call_list

部分的なモック

一部のテストでは、 datetime.date.today()の呼び出しをモックアウトして既知の日付を返したいと思っていましたが、テスト対象のコードが新しい日付オブジェクトを作成するのを防ぎたくありませんでした。 残念ながら、 datetime.date はCで記述されているため、静的なdate.today()メソッドにモンキーパッチを適用することはできませんでした。

日付クラスをモックで効果的にラップすることを含む、これを行う簡単な方法を見つけましたが、コンストラクターへの呼び出しを実際のクラスに渡します(そして実際のインスタンスを返します)。

ここでは、パッチデコレータを使用して、テスト対象のモジュールのdateクラスをモックアウトします。 次に、モック日付クラスのside_effect属性が、実際の日付を返すラムダ関数に設定されます。 モック日付クラスが呼び出されると、実際の日付が作成され、side_effectによって返されます。

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
...     assert mymodule.date.today() == date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)

datetime.date にグローバルにパッチを適用するのではなく、を使用するモジュールのdateにパッチを適用することに注意してください。 パッチを適用する場所を参照してください。

date.today()が呼び出されると、既知の日付が返されますが、date(...)コンストラクターを呼び出すと通常の日付が返されます。 これがないと、テスト対象のコードとまったく同じアルゴリズムを使用して期待される結果を計算する必要があります。これは、古典的なテストのアンチパターンです。

日付コンストラクターへの呼び出しは、mock_date属性(call_countおよびその仲間)に記録されます。これは、テストにも役立つ場合があります。

モック日付やその他の組み込みクラスを処理する別の方法については、このブログエントリで説明されています。


ジェネレータメソッドのモック

Pythonジェネレーターは、 yield ステートメントを使用して、 1 を繰り返したときに一連の値を返す関数またはメソッドです。

ジェネレータオブジェクトを返すために、ジェネレータメソッド/関数が呼び出されます。 その後、繰り返されるのはジェネレータオブジェクトです。 反復のプロトコルメソッドは __ iter __()であるため、 MagicMock を使用してこれをモックできます。

ジェネレーターとして実装された「iter」メソッドを持つクラスの例を次に示します。

>>> class Foo:
...     def iter(self):
...         for i in [1, 2, 3]:
...             yield i
...
>>> foo = Foo()
>>> list(foo.iter())
[1, 2, 3]

このクラス、特にその「iter」メソッドをどのようにモックしますか?

反復から返される値を構成するには( list の呼び出しで暗黙的に)、foo.iter()の呼び出しによって返されるオブジェクトを構成する必要があります。

>>> mock_foo = MagicMock()
>>> mock_foo.iter.return_value = iter([1, 2, 3])
>>> list(mock_foo.iter())
[1, 2, 3]
1
ジェネレータ式やジェネレータの高度な使用法もありますが、ここではそれらについては気にしません。 ジェネレーターの非常に優れた入門書とその強力さ:システムプログラマーのためのジェネレータートリック


すべてのテスト方法に同じパッチを適用する

複数のテストメソッドに複数のパッチを配置する場合、明らかな方法は、パッチデコレータをすべてのメソッドに適用することです。 これは不必要な繰り返しのように感じることがあります。 Python 2.6以降では、 patch()(さまざまな形式すべて)をクラスデコレータとして使用できます。 これにより、クラスのすべてのテストメソッドにパッチが適用されます。 テストメソッドは、名前がtestで始まるメソッドによって識別されます。

>>> @patch('mymodule.SomeClass')
... class MyTest(unittest.TestCase):
...
...     def test_one(self, MockSomeClass):
...         self.assertIs(mymodule.SomeClass, MockSomeClass)
...
...     def test_two(self, MockSomeClass):
...         self.assertIs(mymodule.SomeClass, MockSomeClass)
...
...     def not_a_test(self):
...         return 'something'
...
>>> MyTest('test_one').test_one()
>>> MyTest('test_two').test_two()
>>> MyTest('test_two').not_a_test()
'something'

パッチを管理する別の方法は、パッチメソッドを使用することです:startおよびstop 。 これらを使用すると、パッチをsetUpおよびtearDownメソッドに移動できます。

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         self.patcher = patch('mymodule.foo')
...         self.mock_foo = self.patcher.start()
...
...     def test_foo(self):
...         self.assertIs(mymodule.foo, self.mock_foo)
...
...     def tearDown(self):
...         self.patcher.stop()
...
>>> MyTest('test_foo').run()

この手法を使用する場合は、stopを呼び出して、パッチ適用が「取り消されている」ことを確認する必要があります。 setUpで例外が発生した場合、tearDownは呼び出されないため、これは想像以上に厄介な場合があります。 unittest.TestCase.addCleanup()を使用すると、これが簡単になります。

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         patcher = patch('mymodule.foo')
...         self.addCleanup(patcher.stop)
...         self.mock_foo = patcher.start()
...
...     def test_foo(self):
...         self.assertIs(mymodule.foo, self.mock_foo)
...
>>> MyTest('test_foo').run()

アンバウンドメソッドのモック

今日テストを書いている間、私はアンバウンドメソッドにパッチを当てる必要がありました(インスタンスではなくクラスにメソッドをパッチします)。 どのオブジェクトがこの特定のメソッドを呼び出しているかについてアサートしたいので、最初の引数としてselfを渡す必要がありました。 問題は、これに対してモックでパッチを適用できないことです。これは、バインドされていないメソッドをモックに置き換えると、インスタンスからフェッチされたときにバインドされたメソッドにならないため、セルフパスされないためです。 回避策は、代わりに実際の関数で非バインドメソッドにパッチを適用することです。 patch()デコレータを使用すると、モックを使用してメソッドにパッチを適用するのが非常に簡単になるため、実際の関数を作成する必要があります。

autospec=Trueをパッチに渡すと、 real 関数オブジェクトを使用してパッチが適用されます。 この関数オブジェクトは、置き換えるものと同じ署名を持っていますが、内部のモックに委任します。 以前とまったく同じ方法で、モックが自動作成されます。 ただし、これを使用してクラスのバインドされていないメソッドにパッチを適用すると、モックされた関数がインスタンスからフェッチされた場合にバインドされたメソッドに変換されます。 selfが最初の引数として渡されます。これは、まさに私が望んでいたことです。

>>> class Foo:
...   def foo(self):
...     pass
...
>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
...   mock_foo.return_value = 'foo'
...   foo = Foo()
...   foo.foo()
...
'foo'
>>> mock_foo.assert_called_once_with(foo)

autospec=Trueを使用しない場合、バインドされていないメソッドは代わりにMockインスタンスでパッチが適用され、selfでは呼び出されません。


モックで複数の通話をチェックする

モックには、モックオブジェクトの使用方法に関するアサーションを作成するための優れたAPIがあります。

>>> mock = Mock()
>>> mock.foo_bar.return_value = None
>>> mock.foo_bar('baz', spam='eggs')
>>> mock.foo_bar.assert_called_with('baz', spam='eggs')

モックが一度だけ呼び出される場合は、assert_called_once_with()メソッドを使用できます。このメソッドは、call_countが1つであることも表明します。

>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
>>> mock.foo_bar()
>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
Traceback (most recent call last):
    ...
AssertionError: Expected to be called once. Called 2 times.

assert_called_withassert_called_once_withはどちらも、最新の呼び出しについてアサーションを作成します。 モックが複数回呼び出され、すべてのについてアサーションを作成する場合は、 call_args_list を使用できます。

>>> mock = Mock(return_value=None)
>>> mock(1, 2, 3)
>>> mock(4, 5, 6)
>>> mock()
>>> mock.call_args_list
[call(1, 2, 3), call(4, 5, 6), call()]

call ヘルパーを使用すると、これらの呼び出しに関するアサーションを簡単に作成できます。 予想される呼び出しのリストを作成して、call_args_listと比較できます。 これは、call_args_listのreprと非常によく似ています。

>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]
>>> mock.call_args_list == expected
True

可変引数への対処

別の状況はまれですが、あなたを噛む可能性があります。それは、モックが可変引数で呼び出される場合です。 call_argsおよびcall_args_listは、引数への参照を格納します。 テスト対象のコードによって引数が変更された場合、モックが呼び出されたときの値についてアサーションを作成できなくなります。

問題を示すサンプルコードを次に示します。 'mymodule'で定義されている次の関数を想像してみてください。

def frob(val):
    pass

def grob(val):
    "First frob and then clear val"
    frob(val)
    val.clear()

grobfrobを正しい引数で呼び出すことをテストしようとすると、何が起こるかを見てください。

>>> with patch('mymodule.frob') as mock_frob:
...     val = {6}
...     mymodule.grob(val)
...
>>> val
set()
>>> mock_frob.assert_called_with({6})
Traceback (most recent call last):
    ...
AssertionError: Expected: (({6},), {})
Called with: ((set(),), {})

1つの可能性は、モックが渡した引数をコピーすることです。 同等性をオブジェクトIDに依存するアサーションを実行すると、問題が発生する可能性があります。

side_effect機能を使用する1つのソリューションを次に示します。 モックにside_effect関数を指定すると、side_effectがモックと同じ引数で呼び出されます。 これにより、引数をコピーして、後のアサーションのために保存する機会が得られます。 この例では、 another モックを使用して引数を格納しているため、モックメソッドを使用してアサーションを実行できます。 ここでも、ヘルパー関数がこれを設定します。

>>> from copy import deepcopy
>>> from unittest.mock import Mock, patch, DEFAULT
>>> def copy_call_args(mock):
...     new_mock = Mock()
...     def side_effect(*args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         new_mock(*args, **kwargs)
...         return DEFAULT
...     mock.side_effect = side_effect
...     return new_mock
...
>>> with patch('mymodule.frob') as mock_frob:
...     new_mock = copy_call_args(mock_frob)
...     val = {6}
...     mymodule.grob(val)
...
>>> new_mock.assert_called_with({6})
>>> new_mock.call_args
call({6})

copy_call_argsは、呼び出されるモックで呼び出されます。 アサーションを実行する新しいモックを返します。 side_effect関数は引数のコピーを作成し、そのコピーを使用してnew_mockを呼び出します。

ノート

モックが一度だけ使用される場合は、呼び出された時点で引数をチェックする簡単な方法があります。 side_effect関数内で簡単にチェックできます。

>>> def side_effect(arg):
...     assert arg == {6}
...
>>> mock = Mock(side_effect=side_effect)
>>> mock({6})
>>> mock(set())
Traceback (most recent call last):
    ...
AssertionError

別のアプローチは、引数をコピーする Mock または MagicMock のサブクラスを作成することです( copy.deepcopy()を使用)。 実装例は次のとおりです。

>>> from copy import deepcopy
>>> class CopyingMock(MagicMock):
...     def __call__(self, /, *args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         return super().__call__(*args, **kwargs)
...
>>> c = CopyingMock(return_value=None)
>>> arg = set()
>>> c(arg)
>>> arg.add(1)
>>> c.assert_called_with(set())
>>> c.assert_called_with(arg)
Traceback (most recent call last):
    ...
AssertionError: Expected call: mock({1})
Actual call: mock(set())
>>> c.foo
<CopyingMock name='mock.foo' id='...'>

MockまたはMagicMockをサブクラス化すると、動的に作成されたすべての属性がreturn_valueによって自動的に使用されます。 つまり、CopyingMockのすべての子のタイプもCopyingMockになります。


ネストパッチ

パッチをコンテキストマネージャーとして使用するのは良いことですが、複数のパッチを実行すると、ステートメントがさらに右にインデントされてネストされる可能性があります。

>>> class MyTest(unittest.TestCase):
...
...     def test_foo(self):
...         with patch('mymodule.Foo') as mock_foo:
...             with patch('mymodule.Bar') as mock_bar:
...                 with patch('mymodule.Spam') as mock_spam:
...                     assert mymodule.Foo is mock_foo
...                     assert mymodule.Bar is mock_bar
...                     assert mymodule.Spam is mock_spam
...
>>> original = mymodule.Foo
>>> MyTest('test_foo').test_foo()
>>> assert mymodule.Foo is original

unittest cleanup関数とパッチメソッド:startとstop を使用すると、ネストされたインデントなしで同じ効果を実現できます。 単純なヘルパーメソッドcreate_patchは、パッチを配置し、作成されたモックを返します。

>>> class MyTest(unittest.TestCase):
...
...     def create_patch(self, name):
...         patcher = patch(name)
...         thing = patcher.start()
...         self.addCleanup(patcher.stop)
...         return thing
...
...     def test_foo(self):
...         mock_foo = self.create_patch('mymodule.Foo')
...         mock_bar = self.create_patch('mymodule.Bar')
...         mock_spam = self.create_patch('mymodule.Spam')
...
...         assert mymodule.Foo is mock_foo
...         assert mymodule.Bar is mock_bar
...         assert mymodule.Spam is mock_spam
...
>>> original = mymodule.Foo
>>> MyTest('test_foo').run()
>>> assert mymodule.Foo is original

MagicMockで辞書をモックする

辞書やその他のコンテナオブジェクトをモックして、辞書のように動作させながら、そのオブジェクトへのすべてのアクセスを記録することをお勧めします。

これは、辞書のように動作する MagicMock を使用し、 side_effect を使用して、制御下にある実際の基礎となる辞書への辞書アクセスを委任することで実行できます。

MagicMock__getitem__()および__setitem__()メソッドが呼び出されると(通常の辞書アクセス)、side_effectがキーを使用して呼び出されます([ X154X] 値も)。 何が返されるかを制御することもできます。

MagicMockが使用された後、 call_args_list のような属性を使用して、辞書がどのように使用されたかについてアサートできます。

>>> my_dict = {'a': 1, 'b': 2, 'c': 3}
>>> def getitem(name):
...      return my_dict[name]
...
>>> def setitem(name, val):
...     my_dict[name] = val
...
>>> mock = MagicMock()
>>> mock.__getitem__.side_effect = getitem
>>> mock.__setitem__.side_effect = setitem

ノート

MagicMockを使用する代わりに、Mockを使用し、のみを使用して、特に必要な魔法のメソッドを提供します。

>>> mock = Mock()
>>> mock.__getitem__ = Mock(side_effect=getitem)
>>> mock.__setitem__ = Mock(side_effect=setitem)

3番目のオプションは、MagicMockを使用しますが、dictspec (または spec_set )引数として渡すことで、作成されたMagicMockには、辞書の魔法の方法しかありません。

>>> mock = MagicMock(spec_set=dict)
>>> mock.__getitem__.side_effect = getitem
>>> mock.__setitem__.side_effect = setitem

これらの副作用機能を設定すると、mockは通常の辞書のように動作しますが、アクセスを記録します。 存在しないキーにアクセスしようとすると、 KeyError も発生します。

>>> mock['a']
1
>>> mock['c']
3
>>> mock['d']
Traceback (most recent call last):
    ...
KeyError: 'd'
>>> mock['b'] = 'fish'
>>> mock['d'] = 'eggs'
>>> mock['b']
'fish'
>>> mock['d']
'eggs'

使用後は、通常のモックメソッドと属性を使用してアクセスに関するアサーションを作成できます。

>>> mock.__getitem__.call_args_list
[call('a'), call('c'), call('d'), call('b'), call('d')]
>>> mock.__setitem__.call_args_list
[call('b', 'fish'), call('d', 'eggs')]
>>> my_dict
{'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}

モックサブクラスとその属性

モックをサブクラス化する理由はさまざまです。 1つの理由は、ヘルパーメソッドを追加することかもしれません。 これはばかげた例です:

>>> class MyMock(MagicMock):
...     def has_been_called(self):
...         return self.called
...
>>> mymock = MyMock(return_value=None)
>>> mymock
<MyMock id='...'>
>>> mymock.has_been_called()
False
>>> mymock()
>>> mymock.has_been_called()
True

Mockインスタンスの標準的な動作では、属性と戻り値のモックは、アクセスされるモックと同じタイプです。 これにより、Mock属性がMocksになり、MagicMock属性がMagicMocks 2 になります。 したがって、ヘルパーメソッドを追加するためにサブクラス化する場合は、サブクラスのインスタンスの属性と戻り値のモックでも使用できます。

>>> mymock.foo
<MyMock name='mock.foo' id='...'>
>>> mymock.foo.has_been_called()
False
>>> mymock.foo()
<MyMock name='mock.foo()' id='...'>
>>> mymock.foo.has_been_called()
True

時々これは不便です。 たとえば、 1人のユーザーがモックをサブクラス化してツイストアダプターを作成しています。 これを属性にも適用すると、実際にはエラーが発生します。

Mock(すべてのフレーバー)は、_get_child_mockと呼ばれるメソッドを使用して、属性と戻り値のこれらの「サブモック」を作成します。 このメソッドをオーバーライドすることで、サブクラスが属性に使用されるのを防ぐことができます。 署名は、任意のキーワード引数(**kwargs)を取り、それがモックコンストラクターに渡されることです。

>>> class Subclass(MagicMock):
...     def _get_child_mock(self, /, **kwargs):
...         return MagicMock(**kwargs)
...
>>> mymock = Subclass()
>>> mymock.foo
<MagicMock name='mock.foo' id='...'>
>>> assert isinstance(mymock, Subclass)
>>> assert not isinstance(mymock.foo, Subclass)
>>> assert not isinstance(mymock(), Subclass)
2
このルールの例外は、呼び出し不可能なモックです。 それ以外の場合、呼び出し不可能なモックは呼び出し可能なメソッドを持つことができないため、属性は呼び出し可能なバリアントを使用します。


patch.dictでインポートをモックする

モックが難しい状況の1つは、関数内にローカルインポートがある場合です。 これらは、パッチを適用できるモジュール名前空間のオブジェクトを使用していないため、モックするのが困難です。

一般的に、現地からの輸入は避けなければなりません。 これらは、循環依存を防ぐために行われることがあります。この場合、問題を解決する(コードをリファクタリングする)、またはインポートを遅らせることで「初期費用」を防ぐためのはるかに優れた方法が通常あります。 これは、無条件のローカルインポートよりも優れた方法で解決することもできます(モジュールをクラスまたはモジュール属性として保存し、最初の使用時にのみインポートを実行します)。

それはさておき、mockを使用してインポートの結果に影響を与える方法があります。 インポートすると、 sys.modules ディクショナリからオブジェクトがフェッチされます。 モジュールである必要のないオブジェクトをフェッチすることに注意してください。 モジュールを初めてインポートすると、モジュールオブジェクトが sys.modules に配置されるため、通常、何かをインポートすると、モジュールが返されます。 ただし、これが当てはまる必要はありません。

これは、 patch.dict()を使用して一時的にモックを sys.modules に配置できることを意味します。 このパッチがアクティブな間にインポートすると、モックがフェッチされます。 パッチが完了すると(装飾された関数が終了するか、withステートメントの本体が完了するか、patcher.stop()が呼び出されます)、以前にあったものはすべて安全に復元されます。

これは、「fooble」モジュールをモックアウトする例です。

>>> import sys
>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    import fooble
...    fooble.blob()
...
<Mock name='mock.blob()' id='...'>
>>> assert 'fooble' not in sys.modules
>>> mock.blob.assert_called_once_with()

ご覧のとおり、import foobleは成功しますが、終了時に sys.modules に「愚か者」が残っていません。

これは、from module import nameフォームでも機能します。

>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    from fooble import blob
...    blob.blip()
...
<Mock name='mock.blob.blip()' id='...'>
>>> mock.blob.blip.assert_called_once_with()

少し作業が増えると、パッケージのインポートをモックすることもできます。

>>> mock = Mock()
>>> modules = {'package': mock, 'package.module': mock.module}
>>> with patch.dict('sys.modules', modules):
...    from package.module import fooble
...    fooble()
...
<Mock name='mock.module.fooble()' id='...'>
>>> mock.module.fooble.assert_called_once_with()

呼び出しの順序の追跡と冗長性の低い呼び出しアサーション

Mock クラスを使用すると、 method_calls 属性を使用して、モックオブジェクトに対するメソッド呼び出しの order を追跡できます。 これでは、別々のモックオブジェクト間の呼び出しの順序を追跡することはできませんが、 mock_calls を使用して同じ効果を得ることができます。

モックはmock_callsで子モックへの呼び出しを追跡し、モックの任意の属性にアクセスすると子モックが作成されるため、親モックとは別のモックを作成できます。 これらの子モックへの通話はすべて、親のmock_callsに順番に記録されます。

>>> manager = Mock()
>>> mock_foo = manager.foo
>>> mock_bar = manager.bar
>>> mock_foo.something()
<Mock name='mock.foo.something()' id='...'>
>>> mock_bar.other.thing()
<Mock name='mock.bar.other.thing()' id='...'>
>>> manager.mock_calls
[call.foo.something(), call.bar.other.thing()]

次に、マネージャーモックのmock_calls属性と比較することにより、順序を含む呼び出しについてアサートできます。

>>> expected_calls = [call.foo.something(), call.bar.other.thing()]
>>> manager.mock_calls == expected_calls
True

patchがモックを作成して配置している場合は、 attach_mock()メソッドを使用してモックをマネージャーモックにアタッチできます。 通話を添付した後、マネージャーのmock_callsに記録されます。

>>> manager = MagicMock()
>>> with patch('mymodule.Class1') as MockClass1:
...     with patch('mymodule.Class2') as MockClass2:
...         manager.attach_mock(MockClass1, 'MockClass1')
...         manager.attach_mock(MockClass2, 'MockClass2')
...         MockClass1().foo()
...         MockClass2().bar()
<MagicMock name='mock.MockClass1().foo()' id='...'>
<MagicMock name='mock.MockClass2().bar()' id='...'>
>>> manager.mock_calls
[call.MockClass1(),
call.MockClass1().foo(),
call.MockClass2(),
call.MockClass2().bar()]

多くの呼び出しが行われたが、それらの特定のシーケンスにのみ関心がある場合は、 assert_has_calls()メソッドを使用することもできます。 これは、呼び出しのリストを取ります( call オブジェクトで作成されます)。 その一連の呼び出しが mock_calls にある場合、アサートは成功します。

>>> m = MagicMock()
>>> m().foo().bar().baz()
<MagicMock name='mock().foo().bar().baz()' id='...'>
>>> m.one().two().three()
<MagicMock name='mock.one().two().three()' id='...'>
>>> calls = call.one().two().three().call_list()
>>> m.assert_has_calls(calls)

連鎖呼び出しm.one().two().three()だけがモックに対して行われた呼び出しではありませんが、アサートは引き続き成功します。

モックに複数の呼び出しが行われる場合があり、それらの呼び出しの一部についてのみ主張することに関心があります。 あなたも注文を気にしないかもしれません。 この場合、any_order=Trueassert_has_callsに渡すことができます。

>>> m = MagicMock()
>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')
(...)
>>> calls = [call.fifty('50'), call(1), call.seven(7)]
>>> m.assert_has_calls(calls, any_order=True)

より複雑な引数マッチング

ANY と同じ基本概念を使用して、モックへの引数として使用されるオブジェクトに対してより複雑なアサーションを実行するマッチャーを実装できます。

あるオブジェクトがモックに渡され、デフォルトでオブジェクトID(ユーザー定義クラスのPythonのデフォルト)に基づいて等しいと比較されると想定します。 assert_called_with()を使用するには、まったく同じオブジェクトを渡す必要があります。 このオブジェクトの一部の属性のみに関心がある場合は、これらの属性をチェックするマッチャーを作成できます。

この例では、assert_called_withへの「標準」呼び出しでは不十分であることがわかります。

>>> class Foo:
...     def __init__(self, a, b):
...         self.a, self.b = a, b
...
>>> mock = Mock(return_value=None)
>>> mock(Foo(1, 2))
>>> mock.assert_called_with(Foo(1, 2))
Traceback (most recent call last):
    ...
AssertionError: Expected: call(<__main__.Foo object at 0x...>)
Actual call: call(<__main__.Foo object at 0x...>)

Fooクラスの比較関数は次のようになります。

>>> def compare(self, other):
...     if not type(self) == type(other):
...         return False
...     if self.a != other.a:
...         return False
...     if self.b != other.b:
...         return False
...     return True
...

また、等式操作にこのような比較関数を使用できるマッチャーオブジェクトは、次のようになります。

>>> class Matcher:
...     def __init__(self, compare, some_obj):
...         self.compare = compare
...         self.some_obj = some_obj
...     def __eq__(self, other):
...         return self.compare(self.some_obj, other)
...

これらすべてをまとめると:

>>> match_foo = Matcher(compare, Foo(1, 2))
>>> mock.assert_called_with(match_foo)

Matcherは、比較関数と比較するFooオブジェクトを使用してインスタンス化されます。 assert_called_withでは、Matcher等式メソッドが呼び出されます。このメソッドは、モックが呼び出されたオブジェクトを、マッチャーを作成したオブジェクトと比較します。 それらが一致する場合、assert_called_withは合格し、一致しない場合、 AssertionError が発生します。

>>> match_wrong = Matcher(compare, Foo(3, 4))
>>> mock.assert_called_with(match_wrong)
Traceback (most recent call last):
    ...
AssertionError: Expected: ((<Matcher object at 0x...>,), {})
Called with: ((<Foo object at 0x...>,), {})

少し調整するだけで、比較関数で AssertionError を直接発生させ、より有用な失敗メッセージを提供することができます。

バージョン1.5以降、Pythonテストライブラリ PyHamcrest は、同等のマッチャー( hamcrest.library.integration.match_equality )の形式で、ここで役立つ可能性のある同様の機能を提供します。