Утверждение последовательных вызовов фиктивного метода - PullRequest
126 голосов
/ 30 августа 2011

У макета есть полезный assert_called_with() метод . Однако, насколько я понимаю, это проверяет только последний вызов метода.
Если у меня есть код, который вызывает вызываемый метод 3 раза подряд, каждый раз с разными параметрами, как я могу утверждать эти 3 вызова с их конкретными параметрами?

Ответы [ 4 ]

131 голосов
/ 05 июня 2014

assert_has_calls - еще один подход к этой проблеме.

Из документов:

assert_has_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.assert_has_calls

83 голосов
/ 22 января 2013

Обычно меня не волнует порядок звонков, только то, что они произошли. В этом случае я объединяю 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 может быть более подходящим.

Редактировать

С тех пор как я опубликовал этот ответ, я переосмыслил свой подход к тестированию в целом. Я думаю, стоит упомянуть, что если ваш тест усложняется, возможно, вы тестируете не по назначению или у вас есть проблемы с дизайном. Макеты предназначены для тестирования межобъектной коммуникации в объектно-ориентированном дизайне. Если ваш дизайн не ориентирован на возражения (как в более процедурном или функциональном), макет может быть совершенно неуместным. Возможно, внутри метода слишком много всего происходит, или вы можете тестировать внутренние детали, которые лучше не менять. Я разработал стратегию, упомянутую в этом методе, когда мой код был не очень объектно-ориентированным, и я считаю, что я также проверял внутренние детали, которые лучше было бы оставить без проверки.

37 голосов
/ 30 августа 2011

Вы можете использовать атрибут Mock.call_args_list для сравнения параметров с предыдущими вызовами методов.Что вместе с атрибутом Mock.call_count должно дать вам полный контроль.

16 голосов
/ 17 апреля 2017

Мне всегда приходится искать это снова и снова, так что вот мой ответ.


Утверждение нескольких вызовов методов для различных объектов одного и того же класса

Предположим, у нас есть класс для работы в тяжелых условиях (который мы хотим смоделировать):

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:

In [2]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(13, 17)
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(23, 29)
   ...:    


Теперь вот тестовый пример для heavy_work function:

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


Есть два основных различия.Во-первых, при насмешке над функцией мы устанавливаем ожидаемые вызовы, используя call вместо call.some_method.Во-вторых, мы звоним assert_has_calls на mock_work_function, а не на mock_work_function.return_value.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...