モックには 役に立つassert_called_with()
メソッド があります。ただし、私が理解している限り、これはメソッドのlast呼び出しのみをチェックします。
。
Mock.call_args_list
属性 を使用して、パラメーターを以前のメソッド呼び出しと比較できます。 Mock.call_count
属性 と組み合わせることで、完全な制御が可能になります。
assert_has_calls
は、この問題に対する別のアプローチです。
ドキュメントから:
assert_has_calls(calls、any_order = False)
指定された呼び出しでモックが呼び出されたことをアサートします。 mock_callsリストの呼び出しが確認されます。
Any_orderがFalse(デフォルト)の場合、呼び出しはシーケンシャルでなければなりません。指定された呼び出しの前後に余分な呼び出しがある場合があります。
Any_orderがTrueの場合、呼び出しは任意の順序にすることができますが、それらはすべてmock_callsに出現する必要があります。
例:
>>> from mock import call, Mock
>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)
ソース: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.Mock.assert_has_calls
通常、呼び出しの順序は気にしません。呼び出しが行われたということだけです。その場合、 assert_any_call
と call_count
に関するアサーションを組み合わせます。
>>> import mock
>>> m = mock.Mock()
>>> m(1)
<Mock name='mock()' id='37578160'>
>>> m(2)
<Mock name='mock()' id='37578160'>
>>> m(3)
<Mock name='mock()' id='37578160'>
>>> m.assert_any_call(1)
>>> m.assert_any_call(2)
>>> m.assert_any_call(3)
>>> assert 3 == m.call_count
>>> m.assert_any_call(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "[python path]\lib\site-packages\mock.py", line 891, in assert_any_call
'%s call not found' % expected_string
AssertionError: mock(4) call not found
この方法で行うと、1つのメソッドに渡される呼び出しの大きなリストよりも読みやすく、理解しやすくなります。
順序を気にする場合、または複数の同一の呼び出しが予想される場合は、 assert_has_calls
の方が適切です。
この回答を投稿してから、一般的なテストへのアプローチを見直しました。テストがこれほど複雑になっている場合、テストが不適切であるか、設計に問題がある可能性があることに言及する価値があると思います。モックは、オブジェクト指向設計でオブジェクト間通信をテストするために設計されています。デザインがオブジェクト指向ではない場合(より手続き的または機能的に)、モックは完全に不適切である可能性があります。また、メソッド内であまりにも多くの処理が行われている場合や、モックされないままにしておくのが最適な内部の詳細をテストしている場合もあります。私のコードがあまりオブジェクト指向ではないときにこのメソッドで言及した戦略を開発しました。また、モックをかけずに残しておくとよい内部の詳細もテストしていたと思います。
私はいつもこれを何度も見直さなければならないので、ここに私の答えがあります。
ヘビーデューティークラス(モックしたい)があるとします。
In [1]: class HeavyDuty(object):
...: def __init__(self):
...: import time
...: time.sleep(2) # <- Spends a lot of time here
...:
...: def do_work(self, arg1, arg2):
...: print("Called with %r and %r" % (arg1, arg2))
...:
HeavyDuty
クラスの2つのインスタンスを使用するコードを次に示します。
In [2]: def heavy_work():
...: hd1 = HeavyDuty()
...: hd1.do_work(13, 17)
...: hd2 = HeavyDuty()
...: hd2.do_work(23, 29)
...:
次に、heavy_work
関数のテストケースを示します。
In [3]: from unittest.mock import patch, call
...: def test_heavy_work():
...: expected_calls = [call.do_work(13, 17),call.do_work(23, 29)]
...:
...: with patch('__main__.HeavyDuty') as MockHeavyDuty:
...: heavy_work()
...: MockHeavyDuty.return_value.assert_has_calls(expected_calls)
...:
HeavyDuty
クラスをMockHeavyDuty
でモックしています。すべてのHeavyDuty
インスタンスからのメソッド呼び出しをアサートするには、MockHeavyDuty.return_value.assert_has_calls
の代わりにMockHeavyDuty.assert_has_calls
を参照する必要があります。さらに、expected_calls
のリストでは、呼び出しをアサートすることに関心のあるメソッド名を指定する必要があります。したがって、リストは、単にcall
とは対照的に、call.do_work
の呼び出しで構成されています。
テストケースを実行すると、成功したことがわかります。
In [4]: print(test_heavy_work())
None
heavy_work
関数を変更すると、テストは失敗し、有用なエラーメッセージが生成されます。
In [5]: def heavy_work():
...: hd1 = HeavyDuty()
...: hd1.do_work(113, 117) # <- call args are different
...: hd2 = HeavyDuty()
...: hd2.do_work(123, 129) # <- call args are different
...:
In [6]: print(test_heavy_work())
---------------------------------------------------------------------------
(traceback omitted for clarity)
AssertionError: Calls not found.
Expected: [call.do_work(13, 17), call.do_work(23, 29)]
Actual: [call.do_work(113, 117), call.do_work(123, 129)]
上記とは対照的に、関数への複数の呼び出しをモックする方法を示す例を次に示します。
In [7]: def work_function(arg1, arg2):
...: print("Called with args %r and %r" % (arg1, arg2))
In [8]: from unittest.mock import patch, call
...: def test_work_function():
...: expected_calls = [call(13, 17), call(23, 29)]
...: with patch('__main__.work_function') as mock_work_function:
...: work_function(13, 17)
...: work_function(23, 29)
...: mock_work_function.assert_has_calls(expected_calls)
...:
In [9]: print(test_work_function())
None
主に2つの違いがあります。 1つ目は、関数をモックするときに、call.some_method
を使用する代わりに、call
を使用して予想される呼び出しをセットアップすることです。 2つ目は、assert_has_calls
ではなく、mock_work_function
でmock_work_function.return_value
を呼び出すことです。