Документация Python неофициальный перевод

unittest.mock-examples.md

1154 строк · 70.6 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.11/library/unittest.mock-examples.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [`unittest.mock`](https://python-all.ru/3.11/library/unittest.mock.html#module-unittest.mock) – начало работы89Новое в версии 3.3.1011## Использование Mock1213### Подмена методов Mock1415Типичные случаи использования объектов [`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock):1617- Подмена методов18- Запись вызовов методов объектов1920Возможно, потребуется заменить метод объекта, чтобы проверить, что другая часть системы вызывает его с правильными аргументами:2122```python23>>> real = SomeClass()24>>> real.method = MagicMock(name='method')25>>> real.method(3, 4, 5, key='value')26<MagicMock name='method()' id='...'>27```2829После того как mock был использован (в данном примере `real.method`), он содержит методы и атрибуты, с помощью которых можно проверять, как именно он использовался.3031> **Примечание**32>33> В большинстве этих примеров классы [`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock) и [`MagicMock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.MagicMock) взаимозаменяемы. Поскольку `MagicMock` – более функциональный класс, его имеет смысл использовать по умолчанию.3435После вызова mock его атрибут [`called`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.called) устанавливается в `True`. Что более важно, можно использовать метод [`assert_called_with()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.assert_called_with) или [`assert_called_once_with()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.assert_called_once_with), чтобы проверить, что он был вызван с правильными аргументами.3637Этот пример проверяет, что вызов `ProductionClass().method` приводит к вызову метода `something`:3839```python40>>> class ProductionClass:41...     def method(self):42...         self.something(1, 2, 3)43...     def something(self, a, b, c):44...         pass45...46>>> real = ProductionClass()47>>> real.something = MagicMock()48>>> real.method()49>>> real.something.assert_called_once_with(1, 2, 3)50```5152### Mock для вызовов методов объекта5354В последнем примере мы напрямую подменили метод объекта, чтобы проверить, что он был вызван корректно. Другой типичный случай – передать объект в метод (или какую-то часть тестируемой системы), а затем проверить, что он используется правильным образом.5556Простой класс `ProductionClass` ниже имеет метод `closer`. Если его вызвать с объектом, то он вызовет `close` у этого объекта.5758```python59>>> class ProductionClass:60...     def closer(self, something):61...         something.close()62...63```6465Итак, чтобы протестировать это, нужно передать объект с методом `close` и проверить, что он был вызван корректно.6667```python68>>> real = ProductionClass()69>>> mock = Mock()70>>> real.closer(mock)71>>> mock.close.assert_called_with()72```7374Нам не нужно прилагать усилий, чтобы предоставить метод 'close' в нашем mock. Обращение к close создаёт его. Поэтому, если 'close' ещё не был вызван, то обращение к нему в тесте создаст его, но [`assert_called_with()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.assert_called_with) вызовет исключение, указывающее на ошибку.7576### Подмена классов7778Типичный сценарий – подменить классы, которые инстанциируются тестируемым кодом. При подмене класса он заменяется mock. Экземпляры создаются *вызовом класса*. Это значит, что «экземпляр mock» доступен через возвращаемое значение подменённого класса.7980В примере ниже есть функция `some_function`, которая создаёт экземпляр `Foo` и вызывает его метод. Вызов [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) заменяет класс `Foo` на mock. Экземпляр `Foo` – это результат вызова mock, поэтому он настраивается путём изменения mock [`return_value`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.return_value).8182```python83>>> def some_function():84...     instance = module.Foo()85...     return instance.method()86...87>>> with patch('module.Foo') as mock:88...     instance = mock.return_value89...     instance.method.return_value = 'the result'90...     result = some_function()91...     assert result == 'the result'92```9394### Именование макетов9596Полезно давать вашим mock имена. Имя отображается в repr mock и может помочь, когда mock фигурирует в сообщениях об ошибках тестов. Имя также распространяется на атрибуты и методы mock:9798```python99>>> mock = MagicMock(name='foo')100>>> mock101<MagicMock name='foo' id='...'>102>>> mock.method103<MagicMock name='foo.method' id='...'>104```105106### Отслеживание всех вызовов107108Атрибут [`mock_calls`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.mock_calls) записывает все вызовы дочерних атрибутов mock – а также их дочерних атрибутов.109110```python111>>> mock = MagicMock()112>>> mock.method()113<MagicMock name='mock.method()' id='...'>114>>> mock.attribute.method(10, x=53)115<MagicMock name='mock.attribute.method()' id='...'>116>>> mock.mock_calls117[call.method(), call.attribute.method(10, x=53)]118```119120Если сделать проверку относительно `mock_calls` и были вызваны какие-либо непредвиденные методы, то проверка не пройдёт. Это полезно, поскольку помимо проверки того, что ожидаемые вызовы были сделаны, вы также проверяете, что они были сделаны в правильном порядке и без дополнительных вызовов:121122Объект [`call`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.call) используется для создания списков, которые затем сравниваются с `mock_calls`:123124```python125>>> expected = [call.method(), call.attribute.method(10, x=53)]126>>> mock.mock_calls == expected127True128```129130Однако параметры вызовов, возвращающих макеты, не записываются, а значит, невозможно отслеживать вложенные вызовы, в которых важны параметры, использованные для создания родительских объектов:131132```python133>>> m = Mock()134>>> m.factory(important=True).deliver()135<Mock name='mock.factory().deliver()' id='...'>136>>> m.mock_calls[-1] == call.factory(important=False).deliver()137True138```139140### Установка возвращаемых значений и атрибутов141142Установка возвращаемых значений для mock-объекта очень проста:143144```python145>>> mock = Mock()146>>> mock.return_value = 3147>>> mock()1483149```150151Конечно, то же самое можно сделать для методов mock:152153```python154>>> mock = Mock()155>>> mock.method.return_value = 3156>>> mock.method()1573158```159160Возвращаемое значение также можно задать в конструкторе:161162```python163>>> mock = Mock(return_value=3)164>>> mock()1653166```167168Если требуется задать атрибут на mock, сделайте это:169170```python171>>> mock = Mock()172>>> mock.x = 3173>>> mock.x1743175```176177Иногда требуется имитировать более сложную ситуацию, например `mock.connection.cursor().execute("SELECT 1")`. Если нужно, чтобы этот вызов возвращал список, то придётся настроить результат вложенного вызова.178179Можно использовать [`call`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.call), чтобы построить набор вызовов в виде «цепочки вызовов», как здесь, для удобной последующей проверки утверждений:180181```python182>>> mock = Mock()183>>> cursor = mock.connection.cursor.return_value184>>> cursor.execute.return_value = ['foo']185>>> mock.connection.cursor().execute("SELECT 1")186['foo']187>>> expected = call.connection.cursor().execute("SELECT 1").call_list()188>>> mock.mock_calls189[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]190>>> mock.mock_calls == expected191True192```193194Именно вызов `.call_list()` превращает наш объект вызова в список вызовов, представляющих цепочку.195196### Генерация исключений с помощью моков197198Полезный атрибут – [`side_effect`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.side_effect). Если присвоить ему класс исключения или экземпляр, то при вызове мока будет возбуждено это исключение.199200```python201>>> mock = Mock(side_effect=Exception('Boom!'))202>>> mock()203Traceback (most recent call last):204  ...205Exception: Boom!206```207208### Функции и итерируемые объекты в качестве побочного эффекта209210`side_effect` также можно установить в функцию или итерируемый объект. Сценарий использования `side_effect` в качестве итерируемого объекта – когда мок будет вызываться несколько раз, и каждый вызов должен возвращать другое значение. Если `side_effect` присвоен итерируемый объект, каждый вызов мока возвращает следующий элемент из этого итерируемого объекта:211212```python213>>> mock = MagicMock(side_effect=[4, 5, 6])214>>> mock()2154216>>> mock()2175218>>> mock()2196220```221222Для более продвинутых сценариев, например динамического изменения возвращаемых значений в зависимости от того, с чем вызывается мок, `side_effect` может быть функцией. Функция будет вызвана с теми же аргументами, что и мок. Что бы функция ни вернула, то и будет результатом вызова:223224```python225>>> vals = {(1, 2): 1, (2, 3): 2}226>>> def side_effect(*args):227...     return vals[args]228...229>>> mock = MagicMock(side_effect=side_effect)230>>> mock(1, 2)2311232>>> mock(2, 3)2332234```235236### Мокирование асинхронных итераторов237238Начиная с Python 3.8, `AsyncMock` и `MagicMock` поддерживают мокирование [асинхронных итераторов](https://python-all.ru/3.11/reference/datamodel.html#async-iterators) через `__aiter__`. Атрибут [`return_value`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.return_value) у `__aiter__` можно использовать для задания возвращаемых значений, которые будут использоваться при итерации.239240```python241>>> mock = MagicMock()  # AsyncMock также работает здесь242>>> mock.__aiter__.return_value = [1, 2, 3]243>>> async def main():244...     return [i async for i in mock]245...246>>> asyncio.run(main())247[1, 2, 3]248```249250### Мокирование асинхронного контекстного менеджера251252Начиная с Python 3.8, `AsyncMock` и `MagicMock` поддерживают мокирование [асинхронных контекстных менеджеров](https://python-all.ru/3.11/reference/datamodel.html#async-context-managers) через `__aenter__` и `__aexit__`. По умолчанию `__aenter__` и `__aexit__` являются экземплярами `AsyncMock`, которые возвращают асинхронную функцию.253254```python255>>> class AsyncContextManager:256...     async def __aenter__(self):257...         return self258...     async def __aexit__(self, exc_type, exc, tb):259...         pass260...261>>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock также работает здесь262>>> async def main():263...     async with mock_instance as result:264...         pass265...266>>> asyncio.run(main())267>>> mock_instance.__aenter__.assert_awaited_once()268>>> mock_instance.__aexit__.assert_awaited_once()269```270271### Создание Mock из существующего объекта272273Одна из проблем чрезмерного использования мокирования – это связывание тестов с реализацией моков, а не с реальным кодом. Предположим, есть класс, реализующий `some_method`. В тесте для другого класса вы предоставляете мок этого объекта, который *также* предоставляет `some_method`. Если позже вы реорганизуете первый класс так, что он больше не содержит `some_method`, то тесты продолжат проходить, хотя код уже сломан!274275[`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock) позволяет передать объект в качестве спецификации для мока, используя именованный аргумент *spec*. Обращение к методам/атрибутам мока, отсутствующим в объекте спецификации, немедленно вызовет ошибку атрибута. Если изменить реализацию спецификации, то тесты, использующие этот класс, сразу начнут падать без необходимости создавать экземпляр класса в этих тестах.276277```python278>>> mock = Mock(spec=SomeClass)279>>> mock.old_method()280Traceback (most recent call last):281   ...282AttributeError: object has no attribute 'old_method'283```284285Использование спецификации также позволяет более интеллектуально сопоставлять вызовы, сделанные к моку, независимо от того, переданы ли некоторые параметры как позиционные или именованные аргументы:286287```python288>>> def f(a, b, c): pass289...290>>> mock = Mock(spec=f)291>>> mock(1, 2, 3)292<Mock name='mock()' id='140161580456576'>293>>> mock.assert_called_with(a=1, b=2, c=3)294```295296Если нужно, чтобы такое интеллектуальное сопоставление работало и для вызовов методов мока, можно воспользоваться [авто-спецификацией (auto-speccing)](https://python-all.ru/3.11/library/unittest.mock.html#auto-speccing).297298Если нужна более строгая форма спецификации, запрещающая как установку произвольных атрибутов, так и их получение, можно использовать *spec\_set* вместо *spec*.299300### Использование side\_effect для возврата содержимого для каждого файла301302[`mock_open()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.mock_open) используется для подмены метода [`open()`](https://python-all.ru/3.11/library/functions.html#open). [`side_effect`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.side_effect) можно использовать, чтобы возвращать новый объект Mock при каждом вызове. Это позволяет возвращать разное содержимое для каждого файла, хранящегося в словаре:303304```python305DEFAULT = "default"306data_dict = {"file1": "data1",307             "file2": "data2"}308309def open_side_effect(name):310    return mock_open(read_data=data_dict.get(name, DEFAULT))()311312with patch("builtins.open", side_effect=open_side_effect):313    with open("file1") as file1:314        assert file1.read() == "data1"315316    with open("file2") as file2:317        assert file2.read() == "data2"318319    with open("file3") as file2:320        assert file2.read() == "default"321```322323## Декораторы patch324325> **Примечание**326>327> При использовании [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) важно подменять объекты в том пространстве имён, где они выполняются. Обычно это очевидно, но для краткого руководства прочитайте [where to patch](https://python-all.ru/3.11/library/unittest.mock.html#where-to-patch).328329Частая потребность в тестах – подменить атрибут класса или модуля, например, встроенную функцию или класс в модуле, чтобы проверить, что он инстанциируется. Модули и классы по сути глобальны, поэтому подмену на них нужно отменять после теста, иначе она останется и в других тестах, вызывая труднодиагностируемые проблемы.330331mock предоставляет три удобных декоратора для этого: [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch), [`patch.object()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch.object) и [`patch.dict()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch.dict). `patch` принимает одну строку вида `package.module.Class.attribute` для указания подменяемого атрибута. Также опционально принимает значение, на которое нужно заменить атрибут (или класс, или что-то ещё). ‘patch.object’ принимает объект и имя атрибута для подмены, плюс опционально значение для подмены.332333`patch.object`:334335```python336>>> original = SomeClass.attribute337>>> @patch.object(SomeClass, 'attribute', sentinel.attribute)338... def test():339...     assert SomeClass.attribute == sentinel.attribute340...341>>> test()342>>> assert SomeClass.attribute == original343344>>> @patch('package.module.attribute', sentinel.attribute)345... def test():346...     from package.module import attribute347...     assert attribute is sentinel.attribute348...349>>> test()350```351352Если вы подменяете модуль (включая [`builtins`](https://python-all.ru/3.11/library/builtins.html#module-builtins)), то используйте [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) вместо [`patch.object()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch.object):353354```python355>>> mock = MagicMock(return_value=sentinel.file_handle)356>>> with patch('builtins.open', mock):357...     handle = open('filename', 'r')358...359>>> mock.assert_called_with('filename', 'r')360>>> assert handle == sentinel.file_handle, "incorrect file handle returned"361```362363Имя модуля может быть точечным, в форме `package.module`, если необходимо:364365```python366>>> @patch('package.module.ClassName.attribute', sentinel.attribute)367... def test():368...     from package.module import ClassName369...     assert ClassName.attribute == sentinel.attribute370...371>>> test()372```373374Удобный подход – декорировать непосредственно сами тестовые методы:375376```python377>>> class MyTest(unittest.TestCase):378...     @patch.object(SomeClass, 'attribute', sentinel.attribute)379...     def test_something(self):380...         self.assertEqual(SomeClass.attribute, sentinel.attribute)381...382>>> original = SomeClass.attribute383>>> MyTest('test_something').test_something()384>>> assert SomeClass.attribute == original385```386387Если нужно подменить с помощью Mock, можно использовать [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) с одним аргументом (или [`patch.object()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch.object) с двумя аргументами). Мок будет создан автоматически и передан в тестовую функцию/метод:388389```python390>>> class MyTest(unittest.TestCase):391...     @patch.object(SomeClass, 'static_method')392...     def test_something(self, mock_method):393...         SomeClass.static_method()394...         mock_method.assert_called_with()395...396>>> MyTest('test_something').test_something()397```398399Можно накладывать несколько декораторов патча, используя такой шаблон:400401```python402>>> class MyTest(unittest.TestCase):403...     @patch('package.module.ClassName1')404...     @patch('package.module.ClassName2')405...     def test_something(self, MockClass2, MockClass1):406...         self.assertIs(package.module.ClassName1, MockClass1)407...         self.assertIs(package.module.ClassName2, MockClass2)408...409>>> MyTest('test_something').test_something()410```411412При вложении декораторов patch моки передаются в декорированную функцию в том же порядке, в котором они применяются (обычный порядок применения декораторов в *Python*). Это означает снизу вверх, поэтому в примере выше mock для `test_module.ClassName2` передаётся первым.413414Также есть [`patch.dict()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch.dict) для установки значений в словаре только в пределах области видимости и восстановления словаря в исходное состояние после завершения теста:415416```python417>>> foo = {'key': 'value'}418>>> original = foo.copy()419>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):420...     assert foo == {'newkey': 'newvalue'}421...422>>> assert foo == original423```424425`patch`, `patch.object` и `patch.dict` могут использоваться как контекстные менеджеры.426427При использовании [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) для создания мока можно получить ссылку на мок с помощью формы with с ключевым словом “as”:428429```python430>>> class ProductionClass:431...     def method(self):432...         pass433...434>>> with patch.object(ProductionClass, 'method') as mock_method:435...     mock_method.return_value = None436...     real = ProductionClass()437...     real.method(1, 2, 3)438...439>>> mock_method.assert_called_with(1, 2, 3)440```441442В качестве альтернативы `patch`, `patch.object` и `patch.dict` можно использовать как декораторы классов. При таком использовании это равносильно применению декоратора к каждому методу, имя которого начинается с “test”.443444## Дополнительные примеры445446Вот ещё несколько примеров для немного более сложных сценариев.447448### Мокирование цепочечных вызовов449450Мокирование цепочечных вызовов на самом деле не представляет сложности с mock, если разобраться с атрибутом [`return_value`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.return_value). Когда мок вызывается впервые (или вы обращаетесь к его `return_value` до вызова), создаётся новый [`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock).451452Это означает, что вы можете увидеть, как использовался объект, возвращённый из вызова мокированного объекта, запросив мок `return_value`:453454```python455>>> mock = Mock()456>>> mock().foo(a=2, b=3)457<Mock name='mock().foo()' id='...'>458>>> mock.return_value.foo.assert_called_with(a=2, b=3)459```460461Отсюда один шаг до настройки и проверки утверждений о цепочечных вызовах. Конечно, другая альтернатива – изначально писать код более тестируемым способом…462463Итак, предположим, у нас есть код, который выглядит примерно так:464465```python466>>> class Something:467...     def __init__(self):468...         self.backend = BackendProvider()469...     def method(self):470...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()471...         # ещё код472```473474Предполагая, что `BackendProvider` уже хорошо протестирован, как нам тестировать `method()`? В частности, мы хотим проверить, что участок кода `# more code` использует объект ответа правильным образом.475476Поскольку эта цепочка вызовов строится из атрибута экземпляра, мы можем применить monkey-patch к атрибуту `backend` на экземпляре `Something`. В данном конкретном случае нас интересует только возвращаемое значение из последнего вызова `start_call`, поэтому нам не нужно много настраивать. Допустим, возвращаемый объект является «файлоподобным», поэтому мы обеспечим, чтобы наш объект ответа использовал встроенный [`open()`](https://python-all.ru/3.11/library/functions.html#open) в качестве его `spec`.477478Для этого мы создаём экземпляр мока в качестве нашего мок-бэкенда и создаём для него мок-объект ответа. Чтобы установить ответ как возвращаемое значение для этого последнего `start_call`, мы можем сделать так:479480```python481mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response482```483484Мы можем сделать это чуть более элегантно, используя метод [`configure_mock()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.configure_mock) для прямой установки возвращаемого значения:485486```python487>>> something = Something()488>>> mock_response = Mock(spec=open)489>>> mock_backend = Mock()490>>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}491>>> mock_backend.configure_mock(**config)492```493494С их помощью мы применяем monkey-patch к «mock backend» и можем выполнить реальный вызов:495496```python497>>> something.backend = mock_backend498>>> something.method()499```500501Используя [`mock_calls`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.mock_calls), мы можем проверить цепочечный вызов одним assert. Цепочечный вызов – это несколько вызовов в одной строке кода, поэтому в `mock_calls` будет несколько записей. Мы можем использовать [`call.call_list()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.call.call_list) для создания этого списка вызовов:502503```python504>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()505>>> call_list = chained.call_list()506>>> assert mock_backend.mock_calls == call_list507```508509### Частичное мокирование510511В некоторых тестах я хотел подменить вызов [`datetime.date.today()`](https://python-all.ru/3.11/library/datetime.html#datetime.date.today), чтобы он возвращал известную дату, но не хотел мешать тестируемому коду создавать новые объекты даты. К сожалению, [`datetime.date`](https://python-all.ru/3.11/library/datetime.html#datetime.date) написан на C, и я не мог просто обезьяньим патчем заменить статический метод [`datetime.date.today()`](https://python-all.ru/3.11/library/datetime.html#datetime.date.today).512513Я нашел простой способ сделать это: обернуть класс date в mock, но передавать вызовы конструктора настоящему классу (и возвращать настоящие экземпляры).514515Здесь [`patch decorator`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) используется для мокирования класса `date` в тестируемом модуле. Затем атрибут [`side_effect`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.side_effect) на мок-классе даты устанавливается в лямбда-функцию, возвращающую реальную дату. При вызове мок-класса даты будет создана и возвращена реальная дата с помощью `side_effect`.516517```python518>>> from datetime import date519>>> with patch('mymodule.date') as mock_date:520...     mock_date.today.return_value = date(2010, 10, 8)521...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)522...523...     assert mymodule.date.today() == date(2010, 10, 8)524...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)525```526527Обратите внимание: мы не патчим [`datetime.date`](https://python-all.ru/3.11/library/datetime.html#datetime.date) глобально, мы патчим `date` в модуле, который его *использует*. См. [где патчить](https://python-all.ru/3.11/library/unittest.mock.html#where-to-patch).528529При вызове `date.today()` возвращается известная дата, но вызовы конструктора `date(...)` по-прежнему возвращают обычные даты. Без этого вам пришлось бы вычислять ожидаемый результат, используя тот же алгоритм, что и тестируемый код, что является классическим антипаттерном тестирования.530531Вызовы конструктора даты записываются в атрибуты `mock_date` (`call_count` и другие), что также может быть полезно для ваших тестов.532533Альтернативный способ мокирования дат или других встроенных классов описан в [этой записи блога](https://python-all.ru/3.11/library/unittest.mock-examples.html).534535### Подмена метода-генератора536537Генератор Python – это функция или метод, использующие оператор [`yield`](https://python-all.ru/3.11/reference/simple_stmts.html#yield) для возврата серии значений при итерации по [\[1\]](https://python-all.ru/3.11/library/unittest.mock-examples.html#id3).538539Метод/функция-генератор вызывается для возврата объекта-генератора. Затем итерируются именно по объекту-генератору. Протокольный метод для итерации – [`__iter__()`](https://python-all.ru/3.11/library/stdtypes.html#container.__iter__), поэтому мы можем замокировать его с помощью [`MagicMock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.MagicMock).540541Вот пример класса с методом “iter”, реализованным как генератор:542543```python544>>> class Foo:545...     def iter(self):546...         for i in [1, 2, 3]:547...             yield i548...549>>> foo = Foo()550>>> list(foo.iter())551[1, 2, 3]552```553554Как бы мы мокировали этот класс и, в частности, его метод “iter”?555556Чтобы настроить значения, возвращаемые из итерации (неявно в вызове [`list`](https://python-all.ru/3.11/library/stdtypes.html#list)), нам нужно настроить объект, возвращаемый вызовом `foo.iter()`.557558```python559>>> mock_foo = MagicMock()560>>> mock_foo.iter.return_value = iter([1, 2, 3])561>>> list(mock_foo.iter())562[1, 2, 3]563```564565\[[1](https://python-all.ru/3.11/library/unittest.mock-examples.html#id2)\]566567Существуют также генераторные выражения и более [продвинутые применения](https://python-all.ru/3.11/library/unittest.mock-examples.html) генераторов, но здесь они нас не интересуют. Очень хорошее введение в генераторы и их мощь: [Generator Tricks for Systems Programmers](https://python-all.ru/3.11/library/unittest.mock-examples.html).568569### Применение одного и того же патча к каждому тестовому методу570571Если вам нужно установить несколько патчей для нескольких тестовых методов, очевидный способ – применить декораторы patch к каждому методу. Это может показаться излишним повторением. Вместо этого можно использовать [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) (во всех его формах) как декоратор класса. Это применяет патчи ко всем тестовым методам класса. Тестовый метод определяется как метод, имя которого начинается с `test`:572573```python574>>> @patch('mymodule.SomeClass')575... class MyTest(unittest.TestCase):576...577...     def test_one(self, MockSomeClass):578...         self.assertIs(mymodule.SomeClass, MockSomeClass)579...580...     def test_two(self, MockSomeClass):581...         self.assertIs(mymodule.SomeClass, MockSomeClass)582...583...     def not_a_test(self):584...         return 'something'585...586>>> MyTest('test_one').test_one()587>>> MyTest('test_two').test_two()588>>> MyTest('test_two').not_a_test()589'something'590```591592Альтернативный способ управления подстановками – использовать [методы patch: start и stop](https://python-all.ru/3.11/library/unittest.mock.html#start-and-stop). Они позволяют перенести подстановку в ваши методы `setUp` и `tearDown`.593594```python595>>> class MyTest(unittest.TestCase):596...     def setUp(self):597...         self.patcher = patch('mymodule.foo')598...         self.mock_foo = self.patcher.start()599...600...     def test_foo(self):601...         self.assertIs(mymodule.foo, self.mock_foo)602...603...     def tearDown(self):604...         self.patcher.stop()605...606>>> MyTest('test_foo').run()607```608609Если используется этот приём, необходимо убедиться, что подстановка «отменена» вызовом `stop`. Это может быть сложнее, чем кажется, потому что если в setUp возникнет исключение, to tearDown не вызывается. [`unittest.TestCase.addCleanup()`](https://python-all.ru/3.11/library/unittest.html#unittest.TestCase.addCleanup) упрощает задачу:610611```python612>>> class MyTest(unittest.TestCase):613...     def setUp(self):614...         patcher = patch('mymodule.foo')615...         self.addCleanup(patcher.stop)616...         self.mock_foo = patcher.start()617...618...     def test_foo(self):619...         self.assertIs(mymodule.foo, self.mock_foo)620...621>>> MyTest('test_foo').run()622```623624### Подмена несвязанных методов625626Сегодня при написании тестов мне понадобилось подменить *несвязанный метод* (подменить метод на классе, а не на экземпляре). Мне нужно было, чтобы self передавался в качестве первого аргумента, потому что я хотел делать утверждения о том, какие объекты вызывали этот конкретный метод. Проблема в том, что для этого нельзя использовать mock при подмене, потому что если заменить несвязанный метод на mock, он не станет связанным методом при получении из экземпляра, и поэтому self не будет передан. Обходной путь – подменить несвязанный метод настоящей функцией. Декоратор [`patch()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch) делает подмену методов с помощью mock настолько простой, что необходимость создавать настоящую функцию становится обузой.627628Если передать `autospec=True` в patch, то подмена будет выполнена с помощью *настоящего* объекта-функции. Этот объект-функция имеет ту же сигнатуру, что и заменяемая функция, но делегирует вызовы mock'у под капотом. Mock по-прежнему создаётся автоматически точно так же, как и раньше. Однако это означает, что если использовать его для подмены несвязанного метода в классе, то подменённая функция превратится в связанный метод при получении из экземпляра. В качестве первого аргумента будет передан `self`, что как раз и было нужно:629630```python631>>> class Foo:632...   def foo(self):633...     pass634...635>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:636...   mock_foo.return_value = 'foo'637...   foo = Foo()638...   foo.foo()639...640'foo'641>>> mock_foo.assert_called_once_with(foo)642```643644Если не использовать `autospec=True`, то несвязанный метод подменяется экземпляром Mock и не вызывается с `self`.645646### Проверка множественных вызовов с помощью mock647648У mock есть удобный API для проверки того, как используются объекты mock.649650```python651>>> mock = Mock()652>>> mock.foo_bar.return_value = None653>>> mock.foo_bar('baz', spam='eggs')654>>> mock.foo_bar.assert_called_with('baz', spam='eggs')655```656657Если mock вызывается только один раз, можно использовать метод [`assert_called_once_with()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.assert_called_once_with), который дополнительно проверяет, что [`call_count`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.call_count) равен единице.658659```python660>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')661>>> mock.foo_bar()662>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')663Traceback (most recent call last):664    ...665AssertionError: Expected to be called once. Called 2 times.666```667668И `assert_called_with`, и `assert_called_once_with` проверяют *последний* вызов. Если mock будет вызываться несколько раз и нужно проверить *все* эти вызовы, можно использовать [`call_args_list`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.call_args_list):669670```python671>>> mock = Mock(return_value=None)672>>> mock(1, 2, 3)673>>> mock(4, 5, 6)674>>> mock()675>>> mock.call_args_list676[call(1, 2, 3), call(4, 5, 6), call()]677```678679Вспомогательная функция [`call`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.call) упрощает проверку этих вызовов. Можно сформировать список ожидаемых вызовов и сравнить его с `call_args_list`. Это выглядит очень похоже на строковое представление `call_args_list`:680681```python682>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]683>>> mock.call_args_list == expected684True685```686687### Работа с изменяемыми аргументами688689Ещё одна, хоть и редкая, ситуация, которая может доставить неприятности, – когда mock вызывается с изменяемыми аргументами. `call_args` и `call_args_list` хранят *ссылки* на аргументы. Если аргументы изменяются тестируемым кодом, то становится невозможно проверить, какими были значения на момент вызова mock.690691Вот пример кода, демонстрирующий проблему. Предположим, в 'mymodule' определены следующие функции:692693```python694def frob(val):695    pass696697def grob(val):698    "First frob and then clear val"699    frob(val)700    val.clear()701```702703При попытке проверить, что `grob` вызывает `frob` с правильным аргументом, происходит следующее:704705```python706>>> with patch('mymodule.frob') as mock_frob:707...     val = {6}708...     mymodule.grob(val)709...710>>> val711set()712>>> mock_frob.assert_called_with({6})713Traceback (most recent call last):714    ...715AssertionError: Expected: (({6},), {})716Called with: ((set(),), {})717```718719Одним из возможных решений было бы копирование аргументов, передаваемых в mock. Однако это может вызвать проблемы, если проверки полагаются на тождественность объектов для сравнения.720721Вот одно из решений, использующее функциональность [`side_effect`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.side_effect). Если предоставить функцию `side_effect` для mock, то `side_effect` будет вызываться с теми же аргументами, что и mock. Это даёт возможность скопировать аргументы и сохранить их для последующих проверок. В этом примере используется *другой* mock для хранения аргументов, чтобы можно было задействовать методы mock для проверки. Опять же, вспомогательная функция настраивает это:722723```python724>>> from copy import deepcopy725>>> from unittest.mock import Mock, patch, DEFAULT726>>> def copy_call_args(mock):727...     new_mock = Mock()728...     def side_effect(*args, **kwargs):729...         args = deepcopy(args)730...         kwargs = deepcopy(kwargs)731...         new_mock(*args, **kwargs)732...         return DEFAULT733...     mock.side_effect = side_effect734...     return new_mock735...736>>> with patch('mymodule.frob') as mock_frob:737...     new_mock = copy_call_args(mock_frob)738...     val = {6}739...     mymodule.grob(val)740...741>>> new_mock.assert_called_with({6})742>>> new_mock.call_args743call({6})744```745746`copy_call_args` вызывается с mock'ом, который будет вызван. Она возвращает новый mock, на котором выполняется проверка. Функция `side_effect` создаёт копию аргументов и вызывает наш `new_mock` с этой копией.747748> **Примечание**749>750> Если mock используется только один раз, есть более простой способ проверки аргументов в момент вызова. Можно просто выполнить проверку внутри функции `side_effect`.751>752> ```python753> >>> def side_effect(arg):754> ...     assert arg == {6}755> ...756> >>> mock = Mock(side_effect=side_effect)757> >>> mock({6})758> >>> mock(set())759> Traceback (most recent call last):760>     ...761> AssertionError762> ```763764Альтернативный подход – создать подкласс [`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock) или [`MagicMock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.MagicMock), который копирует аргументы (с помощью [`copy.deepcopy()`](https://python-all.ru/3.11/library/copy.html#copy.deepcopy)). Вот пример реализации:765766```python767>>> from copy import deepcopy768>>> class CopyingMock(MagicMock):769...     def __call__(self, /, *args, **kwargs):770...         args = deepcopy(args)771...         kwargs = deepcopy(kwargs)772...         return super().__call__(*args, **kwargs)773...774>>> c = CopyingMock(return_value=None)775>>> arg = set()776>>> c(arg)777>>> arg.add(1)778>>> c.assert_called_with(set())779>>> c.assert_called_with(arg)780Traceback (most recent call last):781    ...782AssertionError: Expected call: mock({1})783Actual call: mock(set())784>>> c.foo785<CopyingMock name='mock.foo' id='...'>786```787788При создании подкласса `Mock` или `MagicMock` все динамически создаваемые атрибуты и `return_value` будут автоматически использовать ваш подкласс. Это означает, что все дочерние элементы `CopyingMock` также будут иметь тип `CopyingMock`.789790### Вложенные подмены791792Использовать patch как менеджер контекста удобно, но при множественных подстановках можно получить вложенные операторы with, уходящие всё дальше вправо с отступами:793794```python795>>> class MyTest(unittest.TestCase):796...797...     def test_foo(self):798...         with patch('mymodule.Foo') as mock_foo:799...             with patch('mymodule.Bar') as mock_bar:800...                 with patch('mymodule.Spam') as mock_spam:801...                     assert mymodule.Foo is mock_foo802...                     assert mymodule.Bar is mock_bar803...                     assert mymodule.Spam is mock_spam804...805>>> original = mymodule.Foo806>>> MyTest('test_foo').test_foo()807>>> assert mymodule.Foo is original808```809810С помощью функций unittest `cleanup` и [методов patch: start и stop](https://python-all.ru/3.11/library/unittest.mock.html#start-and-stop) можно добиться того же эффекта без вложенных отступов. Простой вспомогательный метод `create_patch` устанавливает подстановку и возвращает созданный mock:811812```python813>>> class MyTest(unittest.TestCase):814...815...     def create_patch(self, name):816...         patcher = patch(name)817...         thing = patcher.start()818...         self.addCleanup(patcher.stop)819...         return thing820...821...     def test_foo(self):822...         mock_foo = self.create_patch('mymodule.Foo')823...         mock_bar = self.create_patch('mymodule.Bar')824...         mock_spam = self.create_patch('mymodule.Spam')825...826...         assert mymodule.Foo is mock_foo827...         assert mymodule.Bar is mock_bar828...         assert mymodule.Spam is mock_spam829...830>>> original = mymodule.Foo831>>> MyTest('test_foo').run()832>>> assert mymodule.Foo is original833```834835### Мокирование словаря с помощью MagicMock836837Может потребоваться заменить словарь (или другой контейнер) mock'ом, записывающим все обращения к нему, но при этом сохраняющим поведение словаря.838839Это можно сделать с помощью [`MagicMock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.MagicMock), который будет вести себя как словарь, и [`side_effect`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.side_effect), делегирующего доступ к словарю реальному нижележащему словарю, находящемуся под нашим контролем.840841При вызове методов [`__getitem__()`](https://python-all.ru/3.11/reference/datamodel.html#object.__getitem__) и [`__setitem__()`](https://python-all.ru/3.11/reference/datamodel.html#object.__setitem__) нашего `MagicMock` (обычный доступ к словарю) вызывается `side_effect` с ключом (а в случае `__setitem__` также и со значением). Также можно управлять возвращаемым значением.842843После использования `MagicMock` можно применять атрибуты, такие как [`call_args_list`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.call_args_list), чтобы проверить, как использовался словарь:844845```python846>>> my_dict = {'a': 1, 'b': 2, 'c': 3}847>>> def getitem(name):848...      return my_dict[name]849...850>>> def setitem(name, val):851...     my_dict[name] = val852...853>>> mock = MagicMock()854>>> mock.__getitem__.side_effect = getitem855>>> mock.__setitem__.side_effect = setitem856```857858> **Примечание**859>860> Альтернатива использованию `MagicMock` – применить `Mock` и предоставить *только* те магические методы, которые нужны:861>862> ```python863> >>> mock = Mock()864> >>> mock.__getitem__ = Mock(side_effect=getitem)865> >>> mock.__setitem__ = Mock(side_effect=setitem)866> ```867>868> *Третий* вариант – использовать `MagicMock`, передавая `dict` в качестве аргумента *spec* (или *spec\_set*), чтобы созданный `MagicMock` имел только магические методы словаря:869>870> ```python871> >>> mock = MagicMock(spec_set=dict)872> >>> mock.__getitem__.side_effect = getitem873> >>> mock.__setitem__.side_effect = setitem874> ```875876С такими функциями бокового эффекта `mock` будет вести себя как обычный словарь, но записывать обращения. Он даже вызовет исключение [`KeyError`](https://python-all.ru/3.11/library/exceptions.html#KeyError), если попытаться обратиться к несуществующему ключу.877878```python879>>> mock['a']8801881>>> mock['c']8823883>>> mock['d']884Traceback (most recent call last):885    ...886KeyError: 'd'887>>> mock['b'] = 'fish'888>>> mock['d'] = 'eggs'889>>> mock['b']890'fish'891>>> mock['d']892'eggs'893```894895После использования можно делать утверждения о доступе, используя обычные методы и атрибуты mock:896897```python898>>> mock.__getitem__.call_args_list899[call('a'), call('c'), call('d'), call('b'), call('d')]900>>> mock.__setitem__.call_args_list901[call('b', 'fish'), call('d', 'eggs')]902>>> my_dict903{'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}904```905906### Подклассы Mock и их атрибуты907908Есть разные причины, по которым может потребоваться создать подкласс [`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock). Одна из причин – добавление вспомогательных методов. Вот тривиальный пример:909910```python911>>> class MyMock(MagicMock):912...     def has_been_called(self):913...         return self.called914...915>>> mymock = MyMock(return_value=None)916>>> mymock917<MyMock id='...'>918>>> mymock.has_been_called()919False920>>> mymock()921>>> mymock.has_been_called()922True923```924925Стандартное поведение для экземпляров `Mock` заключается в том, что атрибуты и возвращаемые значения mock имеют тот же тип, что и mock, к которому они обращаются. Это гарантирует, что атрибуты `Mock` будут `Mocks`, а атрибуты `MagicMock``MagicMocks` [\[2\]](https://python-all.ru/3.11/library/unittest.mock-examples.html#id5). Поэтому если вы создаёте подкласс для добавления вспомогательных методов, они также будут доступны в атрибутах и возвращаемых значениях экземпляров вашего подкласса.926927```python928>>> mymock.foo929<MyMock name='mock.foo' id='...'>930>>> mymock.foo.has_been_called()931False932>>> mymock.foo()933<MyMock name='mock.foo()' id='...'>934>>> mymock.foo.has_been_called()935True936```937938Иногда это неудобно. Например, [один пользователь](https://python-all.ru/3.11/library/unittest.mock-examples.html) создаёт подкласс mock для [адаптера Twisted](https://python-all.ru/3.11/library/unittest.mock-examples.html). Применение этого же поведения к атрибутам приводит к ошибкам.939940`Mock` (во всех его вариантах) использует метод `_get_child_mock` для создания «под-mock» для атрибутов и возвращаемых значений. Вы можете предотвратить использование вашего подкласса для атрибутов, переопределив этот метод. Сигнатура такова: он принимает произвольные именованные аргументы (`**kwargs`), которые затем передаются конструктору mock.941942```python943>>> class Subclass(MagicMock):944...     def _get_child_mock(self, /, **kwargs):945...         return MagicMock(**kwargs)946...947>>> mymock = Subclass()948>>> mymock.foo949<MagicMock name='mock.foo' id='...'>950>>> assert isinstance(mymock, Subclass)951>>> assert not isinstance(mymock.foo, Subclass)952>>> assert not isinstance(mymock(), Subclass)953```954955\[[2](https://python-all.ru/3.11/library/unittest.mock-examples.html#id4)\]956957Исключение из этого правила – невызываемые имитации. Атрибуты используют вызываемый вариант, потому что иначе невызываемые имитации не могли бы иметь вызываемые методы.958959### Имитация импорта с помощью patch.dict960961Одна из ситуаций, где имитация сложна – локальный импорт внутри функции. Такие импорты труднее подменить, потому что они не используют объект из пространства имён модуля, который можно замокать.962963В целом локальных импортов следует избегать. Иногда их используют для предотвращения циклических зависимостей, для чего *обычно* есть гораздо лучшее решение (рефакторинг кода), или для снижения «начальных затрат» путём отложенного импорта. Эту проблему также можно решить лучше, чем безусловный локальный импорт (сохранить модуль как атрибут класса или модуля и выполнять импорт только при первом использовании).964965Тем не менее, есть способ использовать `mock` для влияния на результаты импорта. Импорт извлекает *объект* из словаря [`sys.modules`](https://python-all.ru/3.11/library/sys.html#sys.modules). Обратите внимание, что извлекается *объект*, который не обязательно является модулем. При первом импорте модуля в `sys.modules` помещается объект модуля, поэтому обычно при импорте вы получаете модуль. Однако это не обязательно так.966967Это означает, что можно использовать [`patch.dict()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.patch.dict), чтобы *временно* поместить mock в [`sys.modules`](https://python-all.ru/3.11/library/sys.html#sys.modules). Любые импорты, выполняемые, пока активен этот patch, будут извлекать mock. Когда patch завершается (декорированная функция завершается, тело with-инструкции выполнено или вызывается `patcher.stop()`), то, что было там ранее, будет безопасно восстановлено.968969Вот пример, который имитирует модуль 'fooble'.970971```python972>>> import sys973>>> mock = Mock()974>>> with patch.dict('sys.modules', {'fooble': mock}):975...    import fooble976...    fooble.blob()977...978<Mock name='mock.blob()' id='...'>979>>> assert 'fooble' not in sys.modules980>>> mock.blob.assert_called_once_with()981```982983Как видите, `import fooble` успешно выполняется, но при выходе в [`sys.modules`](https://python-all.ru/3.11/library/sys.html#sys.modules) не остаётся 'fooble'.984985Это также работает для формы `from module import name`:986987```python988>>> mock = Mock()989>>> with patch.dict('sys.modules', {'fooble': mock}):990...    from fooble import blob991...    blob.blip()992...993<Mock name='mock.blob.blip()' id='...'>994>>> mock.blob.blip.assert_called_once_with()995```996997Немного больше усилий, и можно также имитировать импорт пакетов:998999```python1000>>> mock = Mock()1001>>> modules = {'package': mock, 'package.module': mock.module}1002>>> with patch.dict('sys.modules', modules):1003...    from package.module import fooble1004...    fooble()1005...1006<Mock name='mock.module.fooble()' id='...'>1007>>> mock.module.fooble.assert_called_once_with()1008```10091010### Отслеживание порядка вызовов и менее многословные утверждения о вызовах10111012Класс [`Mock`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock) позволяет отслеживать *порядок* вызовов методов на объектах mock через атрибут [`method_calls`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.method_calls). Это не позволяет отслеживать порядок вызовов между разными объектами mock, но мы можем использовать [`mock_calls`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.mock_calls) для достижения того же эффекта.10131014Поскольку mock отслеживают вызовы дочерних mock в `mock_calls`, а обращение к произвольному атрибуту mock создаёт дочерний mock, мы можем создать отдельные mock из родительского. Вызовы этих дочерних mock будут записаны по порядку в `mock_calls` родителя:10151016```python1017>>> manager = Mock()1018>>> mock_foo = manager.foo1019>>> mock_bar = manager.bar1020```10211022```python1023>>> mock_foo.something()1024<Mock name='mock.foo.something()' id='...'>1025>>> mock_bar.other.thing()1026<Mock name='mock.bar.other.thing()' id='...'>1027```10281029```python1030>>> manager.mock_calls1031[call.foo.something(), call.bar.other.thing()]1032```10331034Затем можно делать утверждения о вызовах, включая порядок, сравнивая с атрибутом `mock_calls` управляющего mock:10351036```python1037>>> expected_calls = [call.foo.something(), call.bar.other.thing()]1038>>> manager.mock_calls == expected_calls1039True1040```10411042Если `patch` создаёт и размещает ваши mock, вы можете прикрепить их к управляющему mock с помощью метода [`attach_mock()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.attach_mock). После прикрепления вызовы будут записаны в `mock_calls` управляющего.10431044```python1045>>> manager = MagicMock()1046>>> with patch('mymodule.Class1') as MockClass1:1047...     with patch('mymodule.Class2') as MockClass2:1048...         manager.attach_mock(MockClass1, 'MockClass1')1049...         manager.attach_mock(MockClass2, 'MockClass2')1050...         MockClass1().foo()1051...         MockClass2().bar()1052<MagicMock name='mock.MockClass1().foo()' id='...'>1053<MagicMock name='mock.MockClass2().bar()' id='...'>1054>>> manager.mock_calls1055[call.MockClass1(),1056call.MockClass1().foo(),1057call.MockClass2(),1058call.MockClass2().bar()]1059```10601061Если было много вызовов, но вас интересует только определённая последовательность, можно использовать метод [`assert_has_calls()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls). Он принимает список вызовов (созданный с помощью объекта [`call`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.call)). Если эта последовательность вызовов есть в [`mock_calls`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.mock_calls), утверждение проходит.10621063```python1064>>> m = MagicMock()1065>>> m().foo().bar().baz()1066<MagicMock name='mock().foo().bar().baz()' id='...'>1067>>> m.one().two().three()1068<MagicMock name='mock.one().two().three()' id='...'>1069>>> calls = call.one().two().three().call_list()1070>>> m.assert_has_calls(calls)1071```10721073Даже если цепочка вызовов `m.one().two().three()` – не единственные вызовы, сделанные к mock, утверждение всё равно проходит.10741075Иногда mock получает несколько вызовов, а вас интересуют утверждения только о *некоторых* из них. Возможно, вас не волнует порядок. В этом случае можно передать `any_order=True` в `assert_has_calls`:10761077```python1078>>> m = MagicMock()1079>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')1080(...)1081>>> calls = [call.fifty('50'), call(1), call.seven(7)]1082>>> m.assert_has_calls(calls, any_order=True)1083```10841085### Более сложное сопоставление аргументов10861087Используя ту же базовую концепцию, что и [`ANY`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.ANY), можно реализовать сопоставители для более сложных утверждений об объектах, передаваемых имитациям в качестве аргументов.10881089Предположим, мы ожидаем, что mock будет передан некоторый объект, который по умолчанию сравнивается по идентичности (это поведение Python по умолчанию для пользовательских классов). Чтобы использовать [`assert_called_with()`](https://python-all.ru/3.11/library/unittest.mock.html#unittest.mock.Mock.assert_called_with), нужно передать точно такой же объект. Если нас интересуют только некоторые атрибуты этого объекта, можно создать сопоставитель, который будет проверять эти атрибуты.10901091В этом примере видно, что «стандартного» вызова `assert_called_with` недостаточно:10921093```python1094>>> class Foo:1095...     def __init__(self, a, b):1096...         self.a, self.b = a, b1097...1098>>> mock = Mock(return_value=None)1099>>> mock(Foo(1, 2))1100>>> mock.assert_called_with(Foo(1, 2))1101Traceback (most recent call last):1102    ...1103AssertionError: Expected: call(<__main__.Foo object at 0x...>)1104Actual call: call(<__main__.Foo object at 0x...>)1105```11061107Функция сравнения для класса `Foo` может выглядеть примерно так:11081109```python1110>>> def compare(self, other):1111...     if not type(self) == type(other):1112...         return False1113...     if self.a != other.a:1114...         return False1115...     if self.b != other.b:1116...         return False1117...     return True1118...1119```11201121А объект-сопоставитель, который может использовать такие функции сравнения для операции равенства, будет выглядеть примерно так:11221123```python1124>>> class Matcher:1125...     def __init__(self, compare, some_obj):1126...         self.compare = compare1127...         self.some_obj = some_obj1128...     def __eq__(self, other):1129...         return self.compare(self.some_obj, other)1130...1131```11321133Собираем всё вместе:11341135```python1136>>> match_foo = Matcher(compare, Foo(1, 2))1137>>> mock.assert_called_with(match_foo)1138```11391140`Matcher` создаётся с нашей функцией сравнения и объектом `Foo`, с которым мы хотим сравнивать. В `assert_called_with` будет вызван метод равенства `Matcher`, который сравнивает объект, с которым был вызван mock, с тем, с которым мы создали наш сопоставитель. Если они совпадают, `assert_called_with` проходит, иначе возбуждается [`AssertionError`](https://python-all.ru/3.11/library/exceptions.html#AssertionError):11411142```python1143>>> match_wrong = Matcher(compare, Foo(3, 4))1144>>> mock.assert_called_with(match_wrong)1145Traceback (most recent call last):1146    ...1147AssertionError: Expected: ((<Matcher object at 0x...>,), {})1148Called with: ((<Foo object at 0x...>,), {})1149```11501151При небольшой доработке можно заставить функцию сравнения напрямую возбуждать [`AssertionError`](https://python-all.ru/3.11/library/exceptions.html#AssertionError) и выдавать более полезное сообщение об ошибке.11521153Начиная с версии 1.5, библиотека тестирования Python [PyHamcrest](https://python-all.ru/3.11/library/unittest.mock-examples.html) предоставляет аналогичную функциональность, которая может быть полезна здесь, в виде средства сравнения на равенство ([hamcrest.library.integration.match\_equality](https://python-all.ru/3.11/library/unittest.mock-examples.html)).1154