モックには便利なassert_called_with()
方法があります。ただし、私が理解している限り、これはメソッドの最後の呼び出しのみをチェックします。
モックされたメソッドを連続して3回、異なるパラメーターで3回呼び出すコードがある場合、これらの3つの呼び出しを特定のパラメーターでアサートするにはどうすればよいですか?
モックには便利なassert_called_with()
方法があります。ただし、私が理解している限り、これはメソッドの最後の呼び出しのみをチェックします。
モックされたメソッドを連続して3回、異なるパラメーターで3回呼び出すコードがある場合、これらの3つの呼び出しを特定のパラメーターでアサートするにはどうすればよいですか?
回答:
assert_has_calls
この問題への別のアプローチです。
ドキュメントから:
assert_has_calls (calls、any_order = False)
指定された呼び出しでモックが呼び出されたことをアサートします。mock_callsリストの呼び出しがチェックされます。
any_orderがFalse(デフォルト)の場合、呼び出しは順次である必要があります。指定された呼び出しの前または後に余分な呼び出しがある場合があります。
any_orderがTrueの場合、呼び出しは任意の順序にすることができますが、すべてmock_callsに含める必要があります。
例:
>>> from unittest.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.assert_has_calls
tuple
:isinstance(mock.call(1), tuple)
与えますTrue
。彼らはまた、いくつかのメソッドと属性を追加しました。
side_effect
通常、呼び出しの順序は気にせず、発生したことだけを考慮します。その場合は、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
このようにすると、単一のメソッドに渡される呼び出しの大きなリストよりも読みやすく、理解しやすくなります。
順序を気にする場合、または複数の同一の呼び出しが予想される場合は、assert_has_calls
より適切な場合があります。
この回答を投稿して以来、テスト全般に対する私のアプローチを再考しました。テストがこれほど複雑になっている場合は、テストが不適切であるか、設計上の問題がある可能性があることに言及する価値があると思います。モックは、オブジェクト指向設計でオブジェクト間通信をテストするために設計されています。デザインがオブジェクト指向ではない場合(より手続き的または機能的など)、モックは完全に不適切である可能性があります。また、メソッド内であまりにも多くのことを行っているか、モックされていないままにしておく内部の詳細をテストしている可能性があります。私のコードが非常にオブジェクト指向ではないときにこの方法で言及された戦略を開発しました。また、モックしないでおくのが最善である内部の詳細もテストしていたと思います。
do() if TEST_ENV=='prod' else dont()
)、メソッドの内部ロジックをテストすることは、提案した方法をモックすることによって簡単に実現できます。これの副作用は、バージョンごとのテストを維持することです(たとえば、Google検索API v1とv2の間のコード変更、コードはバージョン1をテストします)
このMock.call_args_list
属性を使用して、パラメーターを以前のメソッド呼び出しと比較できます。これをMock.call_count
属性と組み合わせて使用すると、完全に制御できます。
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.do_work
、単純にではなく、への呼び出しで構成されていますcall
。
テストケースを実行すると、成功したことがわかります。
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
に、を使用する代わりに、を使用して予想される呼び出しをセットアップすることcall.some_method
です。2つ目は、ではなくassert_has_calls
on を呼び出すことです。mock_work_function
mock_work_function.return_value