モックメソッドへの連続した呼び出しのアサート


175

モックには便利なassert_called_with()方法があります。ただし、私が理解している限り、これはメソッドの最後の呼び出しのみをチェックします。
モックされたメソッドを連続して3回、異なるパラメーターで3回呼び出すコードがある場合、これらの3つの呼び出しを特定のパラメーターでアサートするにはどうすればよいですか?

回答:


179

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


9
彼らがリストまたはタプルを使用することもできる新しい「呼び出し」タイプを追加することを選択した少し奇妙なこと...
jaapz

@jaapzそれはサブクラスtupleisinstance(mock.call(1), tuple)与えますTrue。彼らはまた、いくつかのメソッドと属性を追加しました。
jpmc26 2015

13
Mockの初期のバージョンでは単純なタプルを使用していましたが、使用するのが面倒であることが判明しました。各関数呼び出しは(args、kwargs)のタプルを受け取るため、 "foo(123)"が正しく呼び出されたことを確認するには、 "mock.call_args ==((123、)、{})"をアサートする必要があります。 「call(123)」と比較して一口
ジョナサンハートレー

呼び出しの各インスタンスで異なる戻り値が期待される場合はどうしますか?
CodeWithPride 2017

2
@CodeWithPrideそれはより多くの仕事に見えますside_effect
Pigueiras

108

通常、呼び出しの順序は気にせず、発生したことだけを考慮します。その場合は、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より適切な場合があります。

編集する

この回答を投稿して以来、テスト全般に対する私のアプローチを再考しました。テストがこれほど複雑になっている場合は、テストが不適切であるか、設計上の問題がある可能性があることに言及する価値があると思います。モックは、オブジェクト指向設計でオブジェクト間通信をテストするために設計されています。デザインがオブジェクト指向ではない場合(より手続き的または機能的など)、モックは完全に不適切である可能性があります。また、メソッド内であまりにも多くのことを行っているか、モックされていないままにしておく内部の詳細をテストしている可能性があります。私のコードが非常にオブジェクト指向ではないときにこの方法で言及された戦略を開発しました。また、モックしないでおくのが最善である内部の詳細もテストしていたと思います。


@ jpmc26編集について詳しく説明してください。「モックされていないベスト」とはどういう意味ですか?他にどのようにあなたがテストだろう呼び出しがメソッド内で行われている場合
otgw

@memo多くの場合、実際のメソッドを呼び出させる方が良いでしょう。他の方法が壊れていると、テストが壊れる可能性がありますが、それを回避することの価値は、より単純で保守しやすいテストをすることの価値よりも小さくなります。モックするのに最適なタイミングは、他のメソッドへの外部呼び出しがテストするものである場合です(通常、これはある種の結果がそれに渡され、テスト中のコードが結果を返さないことを意味します)。または他のメソッド削除したい外部依存関係(データベース、Webサイト)がある。(技術的には、最後のケースはよりスタブであり、私はそれについて断言するのをためらうでしょう。)
jpmc26

@ jpmc26モッキングは、依存関係の注入やその他のランタイム戦略の選択方法を回避したい場合に便利です。あなたが述べたように、外部サービスを呼び出すことなく、さらに重要なのは、環境に気づくことなく(良いコードにはありませんdo() if TEST_ENV=='prod' else dont())、メソッドの内部ロジックをテストすることは、提案した方法をモックすることによって簡単に実現できます。これの副作用は、バージョンごとのテストを維持することです(たとえば、Google検索API v1とv2の間のコード変更、コードはバージョン1をテストします)
Daniel Dubovski

@DanielDubovskiテストの大部分は入出力ベースである必要があります。それは常に可能であるとは限りませんが、ほとんどの場合それが不可能である場合、おそらく設計上の問題があります。通常は別のコードから取得した値を返す必要があり、依存関係を切り取りたい場合は、通常、スタブが行います。モックが必要なのは、何らかの状態変更関数(おそらく戻り値なし)が呼び出されていることを確認する必要がある場合のみです。(モックとスタブの違いは、スタブを使用した呼び出しではアサートしないことです。)スタブが行う場所でモックを使用すると、テストの保守性が低下します。
jpmc26 2016年

@ jpmc26は外部サービスを一種の出力と呼んでいないのですか?もちろん、call paramsをアサートする代わりに、送信するメッセージを構築するコードをリファクタリングしてテストすることもできますが、IMHOはほとんど同じです。外部APIの呼び出しを再設計することをどのように提案しますか?私はモックを最小限に抑える必要があることに同意します。すべてのイムが言っているのは、外部サービスに送信するデータをテストして、ロジックが期待どおりに動作していることを確認できないことです。
Daniel Dubovski

46

このMock.call_args_list属性を使用して、パラメーターを以前のメソッド呼び出しと比較できます。これをMock.call_count属性と組み合わせて使用​​すると、完全に制御できます。


9
assert_has_calls()?
bavaza

5
assert_has_calls期待される呼び出しが行われたかどうかをチェックするだけで、それが唯一の呼び出しかどうかはチェックしません。
青い染色された2016年

17

私はいつもこれを何度も何度も見なければならないので、これが私の答えです。


同じクラスの異なるオブジェクトに対する複数のメソッド呼び出しの表明

ヘビーデューティクラス(モックしたい)があるとします。

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_callson を呼び出すことです。mock_work_functionmock_work_function.return_value

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.