unittest.mock-examples.md
1> **Источник:** https://python-all.ru/3.4/library/unittest.mock-examples.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 26.5. [`unittest.mock`](https://python-all.ru/3.4/library/unittest.mock.html#module-unittest.mock) – начало работы89Новое в версии 3.3.1011## 26.5.1. Использование Mock1213### 26.5.1.1. Подмена методов с помощью Mock1415Типичные случаи использования объектов [`Mock`](https://python-all.ru/3.4/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.4/library/unittest.mock.html#unittest.mock.Mock) и [`MagicMock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.MagicMock) взаимозаменяемы. Поскольку `MagicMock` – более функциональный класс, разумно использовать его по умолчанию.3435После вызова mock его атрибут [`called`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.called) принимает значение `True`. Что ещё важнее, можно воспользоваться методом [`assert_called_with()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.assert_called_with) или [`assert_called_once_with()`](https://python-all.ru/3.4/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### 26.5.1.2. 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.4/library/unittest.mock.html#unittest.mock.Mock.assert_called_with) вызовет исключение.7576### 26.5.1.3. Подмена классов7778Типичный сценарий – подменить классы, которые инстанциируются тестируемым кодом. При подмене класса он заменяется mock. Экземпляры создаются *вызовом класса*. Это значит, что «экземпляр mock» доступен через возвращаемое значение подменённого класса.7980В примере ниже есть функция `some_function`, которая создаёт экземпляр `Foo` и вызывает его метод. Вызов [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) заменяет класс `Foo` на mock. Экземпляр `Foo` – результат вызова mock, поэтому он настраивается изменением [`return_value`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.return_value) у mock.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### 26.5.1.4. Именование заглушек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### 26.5.1.5. Отслеживание всех вызовов107108Часто требуется отследить не один вызов метода, а несколько. Атрибут [`mock_calls`](https://python-all.ru/3.4/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.4/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### 26.5.1.6. Установка возвращаемых значений и атрибутов131132Установка возвращаемых значений для mock-объекта очень проста:133134```python135>>> mock = Mock()136>>> mock.return_value = 3137>>> mock()1383139```140141Конечно, то же самое можно сделать для методов mock:142143```python144>>> mock = Mock()145>>> mock.method.return_value = 3146>>> mock.method()1473148```149150Возвращаемое значение также можно задать в конструкторе:151152```python153>>> mock = Mock(return_value=3)154>>> mock()1553156```157158Если требуется задать атрибут на mock, сделайте это:159160```python161>>> mock = Mock()162>>> mock.x = 3163>>> mock.x1643165```166167Иногда нужно сэмулировать более сложную ситуацию, например `mock.connection.cursor().execute("SELECT 1")`. Если нужно, чтобы этот вызов вернул список, придётся настроить результат вложенного вызова.168169Можно использовать [`call`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.call) для построения набора вызовов в виде «цепочки вызовов», как здесь, для последующей простой проверки:170171```python172>>> mock = Mock()173>>> cursor = mock.connection.cursor.return_value174>>> cursor.execute.return_value = ['foo']175>>> mock.connection.cursor().execute("SELECT 1")176['foo']177>>> expected = call.connection.cursor().execute("SELECT 1").call_list()178>>> mock.mock_calls179[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]180>>> mock.mock_calls == expected181True182```183184Именно вызов `.call_list()` превращает объект call в список вызовов, представляющих цепочку.185186### 26.5.1.7. Вызов исключений с помощью заглушек187188Полезный атрибут – [`side_effect`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.side_effect). Если задать ему класс исключения или экземпляр, то при вызове mock будет возбуждаться это исключение.189190```python191>>> mock = Mock(side_effect=Exception('Boom!'))192>>> mock()193Traceback (most recent call last):194 ...195Exception: Boom!196```197198### 26.5.1.8. Функции и итерируемые объекты в side\_effect199200`side_effect` также можно задать как функцию или итерируемый объект. Сценарий использования `side_effect` как итерируемого объекта: mock будет вызван несколько раз, и каждый вызов должен возвращать разное значение. Если задать `side_effect` как итерируемый объект, каждый вызов mock будет возвращать следующее значение из этого объекта:201202```python203>>> mock = MagicMock(side_effect=[4, 5, 6])204>>> mock()2054206>>> mock()2075208>>> mock()2096210```211212В более сложных случаях, например для динамического изменения возвращаемых значений в зависимости от аргументов вызова mock, `side_effect` может быть функцией. Функция будет вызвана с теми же аргументами, что и mock. То, что она возвращает, и будет результатом вызова:213214```python215>>> vals = {(1, 2): 1, (2, 3): 2}216>>> def side_effect(*args):217... return vals[args]218...219>>> mock = MagicMock(side_effect=side_effect)220>>> mock(1, 2)2211222>>> mock(2, 3)2232224```225226### 26.5.1.9. Создание mock на основе существующего объекта227228Одна из проблем чрезмерного использования mock – это привязка тестов к реализации mock, а не к реальному коду. Предположим, есть класс, реализующий метод `some_method`. В тесте для другого класса вы предоставляете mock этого объекта, который *также* содержит `some_method`. Если позже вы реорганизуете первый класс, так что он больше не будет иметь `some_method`, то ваши тесты по-прежнему будут проходить, хотя код уже сломан!229230[`Mock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock) позволяет указать объект в качестве спецификации для mock через именованный аргумент *spec*. Обращение к методам/атрибутам mock, отсутствующим в объекте-спецификации, немедленно вызовет ошибку атрибута. Если изменить реализацию спецификации, тесты, использующие этот класс, немедленно начнут падать, причём без необходимости создавать экземпляр класса в этих тестах.231232```python233>>> mock = Mock(spec=SomeClass)234>>> mock.old_method()235Traceback (most recent call last):236 ...237AttributeError: object has no attribute 'old_method'238```239240Использование спецификации также позволяет более интеллектуально сопоставлять вызовы, сделанные к моку, независимо от того, переданы ли некоторые параметры как позиционные или именованные аргументы:241242```python243>>> def f(a, b, c): pass244...245>>> mock = Mock(spec=f)246>>> mock(1, 2, 3)247<Mock name='mock()' id='140161580456576'>248>>> mock.assert_called_with(a=1, b=2, c=3)249```250251Если нужно, чтобы такое интеллектуальное сопоставление работало и для вызовов методов мока, можно воспользоваться [*авто-спецификацией (auto-speccing)*](https://python-all.ru/3.4/library/unittest.mock.html#auto-speccing).252253Если нужна более строгая форма спецификации, запрещающая как установку произвольных атрибутов, так и их получение, можно использовать *spec\_set* вместо *spec*.254255## 26.5.2. Декораторы patch256257> **Примечание**258>259> С [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) важно патчить объекты в том пространстве имён, где они ищутся. Обычно это несложно, но для быстрого ознакомления прочитайте [*где патчить*](https://python-all.ru/3.4/library/unittest.mock.html#where-to-patch).260261Частая потребность в тестах – подменить атрибут класса или модуля, например, встроенную функцию или класс в модуле, чтобы проверить, что он инстанциируется. Модули и классы по сути глобальны, поэтому подмену на них нужно отменять после теста, иначе она останется и в других тестах, вызывая труднодиагностируемые проблемы.262263mock предоставляет три удобных декоратора для этого: [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch), [`patch.object()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch.object) и [`patch.dict()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch.dict). `patch` принимает одну строку вида `package.module.Class.attribute` для указания патчируемого атрибута. Также опционально можно указать значение, на которое заменяется атрибут (или класс, или что угодно). ‘patch.object’ принимает объект и имя атрибута, который требуется пропатчить, плюс опционально значение для замены.264265`patch.object`:266267```python268>>> original = SomeClass.attribute269>>> @patch.object(SomeClass, 'attribute', sentinel.attribute)270... def test():271... assert SomeClass.attribute == sentinel.attribute272...273>>> test()274>>> assert SomeClass.attribute == original275```276277```python278>>> @patch('package.module.attribute', sentinel.attribute)279... def test():280... from package.module import attribute281... assert attribute is sentinel.attribute282...283>>> test()284```285286Если патчится модуль (включая [`builtins`](https://python-all.ru/3.4/library/builtins.html#module-builtins)), то используйте [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) вместо [`patch.object()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch.object):287288```python289>>> mock = MagicMock(return_value=sentinel.file_handle)290>>> with patch('builtins.open', mock):291... handle = open('filename', 'r')292...293>>> mock.assert_called_with('filename', 'r')294>>> assert handle == sentinel.file_handle, "incorrect file handle returned"295```296297Имя модуля может быть точечным, в виде `package.module`, если необходимо:298299```python300>>> @patch('package.module.ClassName.attribute', sentinel.attribute)301... def test():302... from package.module import ClassName303... assert ClassName.attribute == sentinel.attribute304...305>>> test()306```307308Удобный подход – декорировать непосредственно сами тестовые методы:309310```python311>>> class MyTest(unittest2.TestCase):312... @patch.object(SomeClass, 'attribute', sentinel.attribute)313... def test_something(self):314... self.assertEqual(SomeClass.attribute, sentinel.attribute)315...316>>> original = SomeClass.attribute317>>> MyTest('test_something').test_something()318>>> assert SomeClass.attribute == original319```320321Если требуется пропатчить с помощью Mock, можно использовать [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) с одним аргументом (или [`patch.object()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch.object) с двумя аргументами). Mock будет создан автоматически и передан в тестовую функцию/метод:322323```python324>>> class MyTest(unittest2.TestCase):325... @patch.object(SomeClass, 'static_method')326... def test_something(self, mock_method):327... SomeClass.static_method()328... mock_method.assert_called_with()329...330>>> MyTest('test_something').test_something()331```332333Можно накладывать несколько декораторов патча, используя такой шаблон:334335```python336>>> class MyTest(unittest2.TestCase):337... @patch('package.module.ClassName1')338... @patch('package.module.ClassName2')339... def test_something(self, MockClass2, MockClass1):340... self.assertIs(package.module.ClassName1, MockClass1)341... self.assertIs(package.module.ClassName2, MockClass2)342...343>>> MyTest('test_something').test_something()344```345346При вложении декораторов patch моки передаются в декорированную функцию в том же порядке, в котором они применялись (обычный *python* порядок применения декораторов). Это значит снизу вверх, так что в примере выше mock для `test_module.ClassName2` передаётся первым.347348Также существует [`patch.dict()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch.dict) для установки значений в словаре только на время области видимости и восстановления словаря в исходное состояние по завершении теста:349350```python351>>> foo = {'key': 'value'}352>>> original = foo.copy()353>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):354... assert foo == {'newkey': 'newvalue'}355...356>>> assert foo == original357```358359`patch`, `patch.object` и `patch.dict` могут использоваться в качестве контекстных менеджеров.360361Если [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) используется для создания mock, получить ссылку на mock можно с помощью формы with с "as":362363```python364>>> class ProductionClass:365... def method(self):366... pass367...368>>> with patch.object(ProductionClass, 'method') as mock_method:369... mock_method.return_value = None370... real = ProductionClass()371... real.method(1, 2, 3)372...373>>> mock_method.assert_called_with(1, 2, 3)374```375376В качестве альтернативы `patch`, `patch.object` и `patch.dict` могут использоваться как декораторы классов. При таком использовании это эквивалентно применению декоратора отдельно к каждому методу, имя которого начинается с "test".377378## 26.5.3. Дополнительные примеры379380Вот ещё несколько примеров для немного более сложных сценариев.381382### 26.5.3.1. Мокирование цепочечных вызовов383384Мокирование цепочечных вызовов на самом деле несложно с mock, если понять атрибут [`return_value`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.return_value). Когда mock вызывается впервые или вы получаете его `return_value` до вызова, создаётся новый [`Mock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock).385386Это означает, что можно увидеть, как использовался объект, возвращённый вызовом мокированного объекта, анализируя mock `return_value`:387388```python389>>> mock = Mock()390>>> mock().foo(a=2, b=3)391<Mock name='mock().foo()' id='...'>392>>> mock.return_value.foo.assert_called_with(a=2, b=3)393```394395Отсюда всего один шаг до настройки и последующих проверок цепочечных вызовов. Конечно, другой альтернативой является написание кода сразу в более тестируемом виде...396397Итак, предположим, у нас есть код, который выглядит примерно так:398399```python400>>> class Something:401... def __init__(self):402... self.backend = BackendProvider()403... def method(self):404... response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()405... # ещё код406```407408Предполагая, что `BackendProvider` уже хорошо протестирован, как тестировать `method()`? В частности, мы хотим проверить, что участок кода `# more code` правильно использует объект ответа.409410Поскольку эта цепочка вызовов производится через атрибут экземпляра, мы можем применить monkey patch к атрибуту `backend` экземпляра `Something`. В данном случае нас интересует только возвращаемое значение последнего вызова `start_call`, так что настраивать нужно немного. Предположим, что возвращаемый объект является файлоподобным, поэтому мы обеспечим, чтобы наш объект ответа использовал встроенную функцию [`open()`](https://python-all.ru/3.4/library/functions.html#open) в качестве своего `spec`.411412Для этого мы создаём mock-экземпляр в качестве mock-бэкенда и создаём mock-объект ответа. Чтобы установить ответ как возвращаемое значение для этого последнего `start_call`, можно сделать так:413414```python415mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response416```417418Это можно сделать немного элегантнее с помощью метода [`configure_mock()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.configure_mock), чтобы напрямую установить возвращаемое значение:419420```python421>>> something = Something()422>>> mock_response = Mock(spec=open)423>>> mock_backend = Mock()424>>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}425>>> mock_backend.configure_mock(**config)426```427428С их помощью мы применяем monkey-patch к «mock backend» и можем выполнить реальный вызов:429430```python431>>> something.backend = mock_backend432>>> something.method()433```434435Используя [`mock_calls`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.mock_calls), можно проверить цепочечный вызов одним assert. Цепочечный вызов – это несколько вызовов в одной строке кода, поэтому в `mock_calls` будет несколько записей. Для создания этого списка вызовов можно использовать [`call.call_list()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.call.call_list):436437```python438>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()439>>> call_list = chained.call_list()440>>> assert mock_backend.mock_calls == call_list441```442443### 26.5.3.2. Частичное мокирование444445В некоторых тестах я хотел замокировать вызов [`datetime.date.today()`](https://python-all.ru/3.4/library/datetime.html#datetime.date.today), чтобы он возвращал известную дату, но не хотел препятствовать созданию новых объектов date тестируемым кодом. К сожалению, [`datetime.date`](https://python-all.ru/3.4/library/datetime.html#datetime.date) написан на C, поэтому я не мог просто применить monkey patch к статическому методу `date.today()`.446447Я нашел простой способ сделать это: обернуть класс date в mock, но передавать вызовы конструктора настоящему классу (и возвращать настоящие экземпляры).448449Здесь [`patch декоратор`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) используется, чтобы замокировать класс `date` в тестируемом модуле. Затем атрибут `side_effect` в mock-классе date устанавливается в лямбда-функцию, которая возвращает реальную дату. При вызове mock-класса date реальная дата будет создана и возвращена `side_effect`.450451```python452>>> from datetime import date453>>> with patch('mymodule.date') as mock_date:454... mock_date.today.return_value = date(2010, 10, 8)455... mock_date.side_effect = lambda *args, **kw: date(*args, **kw)456...457... assert mymodule.date.today() == date(2010, 10, 8)458... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)459...460```461462Обратите внимание, что мы не патчим [`datetime.date`](https://python-all.ru/3.4/library/datetime.html#datetime.date) глобально, мы патчим `date` в модуле, который *использует* его. См. [*где патчить*](https://python-all.ru/3.4/library/unittest.mock.html#where-to-patch).463464Когда вызывается `date.today()`, возвращается известная дата, но вызовы конструктора `date(...)` по-прежнему возвращают обычные даты. Без этого можно оказаться в ситуации, когда приходится вычислять ожидаемый результат с использованием того же алгоритма, что и тестируемый код, что является классическим антипаттерном тестирования.465466Вызовы конструктора date записываются в атрибутах `mock_date` (`call_count` и подобных), что также может быть полезно для ваших тестов.467468Альтернативный способ мокирования дат или других встроенных классов описан в [этой записи блога](https://python-all.ru/3.4/library/unittest.mock-examples.html).469470### 26.5.3.3. Мокирование метода-генератора471472Генератор Python – это функция или метод, использующий оператор [`yield`](https://python-all.ru/3.4/reference/simple_stmts.html#yield) для возврата последовательности значений при итерации [\[1\]](https://python-all.ru/3.4/library/unittest.mock-examples.html#id3).473474Метод/функция-генератор вызывается для возврата объекта-генератора. Затем именно объект-генератор итерируется. Метод протокола для итерации – [`__iter__()`](https://python-all.ru/3.4/library/stdtypes.html#container.__iter__), поэтому мы можем замокировать его с помощью [`MagicMock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.MagicMock).475476Вот пример класса с методом “iter”, реализованным как генератор:477478```python479>>> class Foo:480... def iter(self):481... for i in [1, 2, 3]:482... yield i483...484>>> foo = Foo()485>>> list(foo.iter())486[1, 2, 3]487```488489Как бы мы мокировали этот класс и, в частности, его метод “iter”?490491Чтобы настроить значения, возвращаемые при итерации (неявно при вызове [`list`](https://python-all.ru/3.4/library/stdtypes.html#list)), необходимо настроить объект, возвращаемый вызовом `foo.iter()`.492493```python494>>> mock_foo = MagicMock()495>>> mock_foo.iter.return_value = iter([1, 2, 3])496>>> list(mock_foo.iter())497[1, 2, 3]498```499500| [\[1\]](https://python-all.ru/3.4/library/unittest.mock-examples.html#id2) | Существуют также генераторные выражения и более [продвинутые применения](https://python-all.ru/3.4/library/unittest.mock-examples.html) генераторов, но здесь они нас не интересуют. Очень хорошее введение в генераторы и их мощь: [Generator Tricks for Systems Programmers](https://python-all.ru/3.4/library/unittest.mock-examples.html). |501| --- | --- |502503### 26.5.3.4. Применение одного и того же патча к каждому тестовому методу504505Если требуется применить несколько патчей для нескольких тестовых методов, очевидный способ – применить декораторы патчей к каждому методу. Это может показаться излишним повторением. Начиная с Python 2.6 можно использовать [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) (во всех его формах) в качестве декоратора класса. Это применяет патчи ко всем тестовым методам класса. Тестовые методы определяются по именам, начинающимся с `test`:506507```python508>>> @patch('mymodule.SomeClass')509... class MyTest(TestCase):510...511... def test_one(self, MockSomeClass):512... self.assertIs(mymodule.SomeClass, MockSomeClass)513...514... def test_two(self, MockSomeClass):515... self.assertIs(mymodule.SomeClass, MockSomeClass)516...517... def not_a_test(self):518... return 'something'519...520>>> MyTest('test_one').test_one()521>>> MyTest('test_two').test_two()522>>> MyTest('test_two').not_a_test()523'something'524```525526Альтернативный способ управления патчами – использовать [*методы patch: start и stop*](https://python-all.ru/3.4/library/unittest.mock.html#start-and-stop). Они позволяют перенести установку патчей в методы `setUp` и `tearDown`.527528```python529>>> class MyTest(TestCase):530... def setUp(self):531... self.patcher = patch('mymodule.foo')532... self.mock_foo = self.patcher.start()533...534... def test_foo(self):535... self.assertIs(mymodule.foo, self.mock_foo)536...537... def tearDown(self):538... self.patcher.stop()539...540>>> MyTest('test_foo').run()541```542543Если используется этот приём, необходимо убедиться, что патч «отменён» вызовом `stop`. Это может быть сложнее, чем кажется, поскольку если в setUp возникнет исключение, tearDown не вызывается. [`unittest.TestCase.addCleanup()`](https://python-all.ru/3.4/library/unittest.html#unittest.TestCase.addCleanup) упрощает эту задачу:544545```python546>>> class MyTest(TestCase):547... def setUp(self):548... patcher = patch('mymodule.foo')549... self.addCleanup(patcher.stop)550... self.mock_foo = patcher.start()551...552... def test_foo(self):553... self.assertIs(mymodule.foo, self.mock_foo)554...555>>> MyTest('test_foo').run()556```557558### 26.5.3.5. Мокирование несвязанных методов559560При написании тестов мне потребовалось исправить *несвязанный метод* (исправить метод на уровне класса, а не экземпляра). Мне нужно было, чтобы self передавался первым аргументом, потому что я хотел проверять, какие объекты вызывали этот конкретный метод. Проблема в том, что для этого нельзя использовать мок: если заменить несвязанный метод на мок, тот не станет связанным методом при получении из экземпляра, и self не будет передан. Обходной путь – исправить несвязанный метод настоящей функцией. Декоратор [`patch()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch) настолько упрощает замену методов моками, что создание настоящей функции становится обременительным.561562Если передать `autospec=True` в patch, то патч будет выполнен с использованием *настоящего* объекта функции. Этот объект функции имеет ту же сигнатуру, что и заменяемый, но делегирует вызовы моку под капотом. Мок по-прежнему создаётся автоматически, как и раньше. Однако это означает, что если использовать его для замены несвязанного метода класса, то замоканная функция превратится в связанный метод при получении из экземпляра. В неё будет передан `self` в качестве первого аргумента – именно то, что мне было нужно:563564```python565>>> class Foo:566... def foo(self):567... pass568...569>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:570... mock_foo.return_value = 'foo'571... foo = Foo()572... foo.foo()573...574'foo'575>>> mock_foo.assert_called_once_with(foo)576```577578Если не использовать `autospec=True`, то несвязанный метод заменяется экземпляром Mock и не вызывается с `self`.579580### 26.5.3.6. Проверка множественных вызовов с помощью mock581582У mock есть удобный API для проверки того, как используются объекты mock.583584```python585>>> mock = Mock()586>>> mock.foo_bar.return_value = None587>>> mock.foo_bar('baz', spam='eggs')588>>> mock.foo_bar.assert_called_with('baz', spam='eggs')589```590591Если мок вызывается только один раз, можно использовать метод `assert_called_once_with()`, который также проверяет, что `call_count` равен единице.592593```python594>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')595>>> mock.foo_bar()596>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')597Traceback (most recent call last):598 ...599AssertionError: Expected to be called once. Called 2 times.600```601602Оба метода `assert_called_with` и `assert_called_once_with` проверяют *последний* вызов. Если мок будет вызываться несколько раз, и требуется проверить *все* эти вызовы, можно использовать [`call_args_list`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.call_args_list):603604```python605>>> mock = Mock(return_value=None)606>>> mock(1, 2, 3)607>>> mock(4, 5, 6)608>>> mock()609>>> mock.call_args_list610[call(1, 2, 3), call(4, 5, 6), call()]611```612613Вспомогательный объект [`call`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.call) упрощает проверку этих вызовов. Можно составить список ожидаемых вызовов и сравнить его с `call_args_list`. Это очень похоже на repr от `call_args_list`:614615```python616>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]617>>> mock.call_args_list == expected618True619```620621### 26.5.3.7. Работа с изменяемыми аргументами622623Ещё одна редкая, но коварная ситуация возникает, когда мок вызывается с изменяемыми аргументами. `call_args` и `call_args_list` хранят *ссылки* на аргументы. Если тестируемый код изменяет аргументы, то уже невозможно проверить, какими были значения на момент вызова мока.624625Вот пример кода, демонстрирующий проблему. Предположим, в 'mymodule' определены следующие функции:626627```python628def frob(val):629 pass630631def grob(val):632 "First frob and then clear val"633 frob(val)634 val.clear()635```636637При попытке проверить, что `grob` вызывает `frob` с правильным аргументом, происходит следующее:638639```python640>>> with patch('mymodule.frob') as mock_frob:641... val = {6}642... mymodule.grob(val)643...644>>> val645set()646>>> mock_frob.assert_called_with({6})647Traceback (most recent call last):648 ...649AssertionError: Expected: (({6},), {})650Called with: ((set(),), {})651```652653Одним из возможных решений было бы копирование аргументов, передаваемых в mock. Однако это может вызвать проблемы, если проверки полагаются на тождественность объектов для сравнения.654655Вот одно из решений, использующее функциональность `side_effect`. Если предоставить функцию `side_effect` для мока, то `side_effect` будет вызвана с теми же аргументами, что и мок. Это даёт возможность скопировать аргументы и сохранить их для последующих проверок. В этом примере я использую *другой* мок для хранения аргументов, чтобы затем использовать методы мока для проверки. Опять же, вспомогательная функция настраивает это.656657```python658>>> from copy import deepcopy659>>> from unittest.mock import Mock, patch, DEFAULT660>>> def copy_call_args(mock):661... new_mock = Mock()662... def side_effect(*args, **kwargs):663... args = deepcopy(args)664... kwargs = deepcopy(kwargs)665... new_mock(*args, **kwargs)666... return DEFAULT667... mock.side_effect = side_effect668... return new_mock669...670>>> with patch('mymodule.frob') as mock_frob:671... new_mock = copy_call_args(mock_frob)672... val = {6}673... mymodule.grob(val)674...675>>> new_mock.assert_called_with({6})676>>> new_mock.call_args677call({6})678```679680Функция `copy_call_args` вызывается с моком, который будет вызван. Она возвращает новый мок, для которого выполняется проверка. Функция `side_effect` создаёт копию аргументов и вызывает `new_mock` с этой копией.681682> **Примечание**683>684> Если мок будет использован только один раз, есть более простой способ проверить аргументы в момент их передачи. Можно просто выполнить проверку внутри функции `side_effect`.685>686> ```python687> >>> def side_effect(arg):688> ... assert arg == {6}689> ...690> >>> mock = Mock(side_effect=side_effect)691> >>> mock({6})692> >>> mock(set())693> Traceback (most recent call last):694> ...695> AssertionError696> ```697698Альтернативный подход – создать подкласс [`Mock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock) или [`MagicMock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.MagicMock), который копирует аргументы (с помощью [`copy.deepcopy()`](https://python-all.ru/3.4/library/copy.html#copy.deepcopy)). Вот пример реализации:699700```python701>>> from copy import deepcopy702>>> class CopyingMock(MagicMock):703... def __call__(self, *args, **kwargs):704... args = deepcopy(args)705... kwargs = deepcopy(kwargs)706... return super(CopyingMock, self).__call__(*args, **kwargs)707...708>>> c = CopyingMock(return_value=None)709>>> arg = set()710>>> c(arg)711>>> arg.add(1)712>>> c.assert_called_with(set())713>>> c.assert_called_with(arg)714Traceback (most recent call last):715 ...716AssertionError: Expected call: mock({1})717Actual call: mock(set())718>>> c.foo719<CopyingMock name='mock.foo' id='...'>720```721722При создании подкласса `Mock` или `MagicMock` все динамически создаваемые атрибуты и `return_value` будут автоматически использовать ваш подкласс. Это означает, что все дочерние объекты `CopyingMock` также будут иметь тип `CopyingMock`.723724### 26.5.3.8. Вложение патчей725726Использовать patch как менеджер контекста удобно, но при множественных подстановках можно получить вложенные операторы with, уходящие всё дальше вправо с отступами:727728```python729>>> class MyTest(TestCase):730...731... def test_foo(self):732... with patch('mymodule.Foo') as mock_foo:733... with patch('mymodule.Bar') as mock_bar:734... with patch('mymodule.Spam') as mock_spam:735... assert mymodule.Foo is mock_foo736... assert mymodule.Bar is mock_bar737... assert mymodule.Spam is mock_spam738...739>>> original = mymodule.Foo740>>> MyTest('test_foo').test_foo()741>>> assert mymodule.Foo is original742```743744С помощью функций cleanup из unittest `cleanup` и методов [*patch: start и stop*](https://python-all.ru/3.4/library/unittest.mock.html#start-and-stop) можно добиться того же эффекта без вложенных отступов. Простой вспомогательный метод `create_patch` устанавливает патч и возвращает созданный мок:745746```python747>>> class MyTest(TestCase):748...749... def create_patch(self, name):750... patcher = patch(name)751... thing = patcher.start()752... self.addCleanup(patcher.stop)753... return thing754...755... def test_foo(self):756... mock_foo = self.create_patch('mymodule.Foo')757... mock_bar = self.create_patch('mymodule.Bar')758... mock_spam = self.create_patch('mymodule.Spam')759...760... assert mymodule.Foo is mock_foo761... assert mymodule.Bar is mock_bar762... assert mymodule.Spam is mock_spam763...764>>> original = mymodule.Foo765>>> MyTest('test_foo').run()766>>> assert mymodule.Foo is original767```768769### 26.5.3.9. Мокирование словаря с помощью MagicMock770771Может потребоваться заменить словарь (или другой контейнер) mock'ом, записывающим все обращения к нему, но при этом сохраняющим поведение словаря.772773Это можно сделать с помощью [`MagicMock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.MagicMock), который будет вести себя как словарь, используя [`side_effect`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.side_effect) для делегирования доступа к словарю реальному словарю под нашим контролем.774775Когда вызываются методы [`__getitem__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__getitem__) и [`__setitem__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__setitem__) нашего `MagicMock` (обычный доступ к словарю), `side_effect` вызывается с ключом (а в случае `__setitem__` – также со значением). Также можно управлять возвращаемым значением.776777После использования `MagicMock` можно применить атрибуты, такие как [`call_args_list`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.call_args_list), чтобы проверить, как использовался словарь:778779```python780>>> my_dict = {'a': 1, 'b': 2, 'c': 3}781>>> def getitem(name):782... return my_dict[name]783...784>>> def setitem(name, val):785... my_dict[name] = val786...787>>> mock = MagicMock()788>>> mock.__getitem__.side_effect = getitem789>>> mock.__setitem__.side_effect = setitem790```791792> **Примечание**793>794> Альтернатива использованию `MagicMock` – использовать `Mock` и *только* предоставить те магические методы, которые нужны:795>796> ```python797> >>> mock = Mock()798> >>> mock.__getitem__ = Mock(side_effect=getitem)799> >>> mock.__setitem__ = Mock(side_effect=setitem)800> ```801>802> *Третий* вариант – использовать `MagicMock`, но передать `dict` в качестве аргумента *spec* (или *spec\_set*), чтобы созданный `MagicMock` имел только магические методы словаря:803>804> ```python805> >>> mock = MagicMock(spec_set=dict)806> >>> mock.__getitem__.side_effect = getitem807> >>> mock.__setitem__.side_effect = setitem808> ```809810При наличии таких функций side\_effect `mock` будет вести себя как обычный словарь, но записывая обращения. Он даже возбуждает [`KeyError`](https://python-all.ru/3.4/library/exceptions.html#KeyError) при попытке доступа к несуществующему ключу.811812```python813>>> mock['a']8141815>>> mock['c']8163817>>> mock['d']818Traceback (most recent call last):819 ...820KeyError: 'd'821>>> mock['b'] = 'fish'822>>> mock['d'] = 'eggs'823>>> mock['b']824'fish'825>>> mock['d']826'eggs'827```828829После использования можно делать утверждения о доступе, используя обычные методы и атрибуты mock:830831```python832>>> mock.__getitem__.call_args_list833[call('a'), call('c'), call('d'), call('b'), call('d')]834>>> mock.__setitem__.call_args_list835[call('b', 'fish'), call('d', 'eggs')]836>>> my_dict837{'a': 1, 'c': 3, 'b': 'fish', 'd': 'eggs'}838```839840### 26.5.3.10. Подклассы Mock и их атрибуты841842Есть разные причины, по которым может потребоваться создать подкласс [`Mock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock). Одна из причин – добавление вспомогательных методов. Вот глупый пример:843844```python845>>> class MyMock(MagicMock):846... def has_been_called(self):847... return self.called848...849>>> mymock = MyMock(return_value=None)850>>> mymock851<MyMock id='...'>852>>> mymock.has_been_called()853False854>>> mymock()855>>> mymock.has_been_called()856True857```858859Стандартное поведение для экземпляров `Mock` заключается в том, что атрибуты и возвращаемое значение моков имеют тот же тип, что и мок, к которому они относятся. Это гарантирует, что атрибуты `Mock` являются `Mocks`, а атрибуты `MagicMock` – `MagicMocks` [\[2\]](https://python-all.ru/3.4/library/unittest.mock-examples.html#id5). Поэтому, если вы создаёте подкласс для добавления вспомогательных методов, они также будут доступны для атрибутов и возвращаемого значения моков экземпляров вашего подкласса.860861```python862>>> mymock.foo863<MyMock name='mock.foo' id='...'>864>>> mymock.foo.has_been_called()865False866>>> mymock.foo()867<MyMock name='mock.foo()' id='...'>868>>> mymock.foo.has_been_called()869True870```871872Иногда это неудобно. Например, [один пользователь](https://python-all.ru/3.4/library/unittest.mock-examples.html) создаёт подкласс mock для [адаптера Twisted](https://python-all.ru/3.4/library/unittest.mock-examples.html). Применение этого же поведения к атрибутам приводит к ошибкам.873874`Mock` (во всех его разновидностях) использует метод `_get_child_mock` для создания этих «под-заглушек» для атрибутов и возвращаемых значений. Чтобы предотвратить использование вашего подкласса для атрибутов, можно переопределить этот метод. Его сигнатура такова: он принимает произвольные именованные аргументы (`**kwargs`), которые затем передаются конструктору заглушки:875876```python877>>> class Subclass(MagicMock):878... def _get_child_mock(self, **kwargs):879... return MagicMock(**kwargs)880...881>>> mymock = Subclass()882>>> mymock.foo883<MagicMock name='mock.foo' id='...'>884>>> assert isinstance(mymock, Subclass)885>>> assert not isinstance(mymock.foo, Subclass)886>>> assert not isinstance(mymock(), Subclass)887```888889| [\[2\]](https://python-all.ru/3.4/library/unittest.mock-examples.html#id4) | Исключение из этого правила – невызываемые имитации. Атрибуты используют вызываемый вариант, потому что иначе невызываемые имитации не могли бы иметь вызываемые методы. |890| --- | --- |891892### 26.5.3.11. Имитация импорта с помощью patch.dict893894Одна из ситуаций, где имитация сложна – локальный импорт внутри функции. Такие импорты труднее подменить, потому что они не используют объект из пространства имён модуля, который можно замокать.895896В целом локальных импортов следует избегать. Иногда их используют для предотвращения циклических зависимостей, для чего *обычно* есть гораздо лучшее решение (рефакторинг кода), или для снижения «начальных затрат» путём отложенного импорта. Эту проблему также можно решить лучше, чем безусловный локальный импорт (сохранить модуль как атрибут класса или модуля и выполнять импорт только при первом использовании).897898Кроме того, существует способ использовать `mock` для влияния на результаты импорта. Импорт извлекает *объект* из словаря [`sys.modules`](https://python-all.ru/3.4/library/sys.html#sys.modules). Обратите внимание, что он извлекает *объект*, который не обязательно является модулем. При первом импорте модуля в *sys.modules* помещается объект модуля, так что обычно при импорте чего-либо вы получаете модуль. Однако это не обязательно так.899900Это означает, что вы можете использовать [`patch.dict()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.patch.dict) для *временного* размещения заглушки в [`sys.modules`](https://python-all.ru/3.4/library/sys.html#sys.modules). Любой импорт во время действия этой заплатки будет извлекать заглушку. Когда заплатка завершена (декорированная функция завершается, тело оператора with завершено или вызывается `patcher.stop()`), все, что было там ранее, будет безопасно восстановлено.901902Вот пример, который имитирует модуль 'fooble'.903904```python905>>> mock = Mock()906>>> with patch.dict('sys.modules', {'fooble': mock}):907... import fooble908... fooble.blob()909...910<Mock name='mock.blob()' id='...'>911>>> assert 'fooble' not in sys.modules912>>> mock.blob.assert_called_once_with()913```914915Как видите, `import fooble` выполняется успешно, но при выходе в [`sys.modules`](https://python-all.ru/3.4/library/sys.html#sys.modules) не остаётся никакого ‘fooble’.916917Это также работает для формы `from module import name`:918919```python920>>> mock = Mock()921>>> with patch.dict('sys.modules', {'fooble': mock}):922... from fooble import blob923... blob.blip()924...925<Mock name='mock.blob.blip()' id='...'>926>>> mock.blob.blip.assert_called_once_with()927```928929Немного больше усилий, и можно также имитировать импорт пакетов:930931```python932>>> mock = Mock()933>>> modules = {'package': mock, 'package.module': mock.module}934>>> with patch.dict('sys.modules', modules):935... from package.module import fooble936... fooble()937...938<Mock name='mock.module.fooble()' id='...'>939>>> mock.module.fooble.assert_called_once_with()940```941942### 26.5.3.12. Отслеживание порядка вызовов и менее многословные утверждения о вызовах943944Класс [`Mock`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock) позволяет отслеживать *порядок* вызовов методов для объектов-заглушек через атрибут [`method_calls`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.method_calls). Это не позволяет отслеживать порядок вызовов между отдельными объектами-заглушками, однако мы можем использовать [`mock_calls`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.mock_calls) для достижения того же эффекта.945946Поскольку заглушки отслеживают вызовы к дочерним заглушкам в `mock_calls`, а доступ к произвольному атрибуту заглушки создает дочернюю заглушку, мы можем создавать отдельные заглушки от родительской. Все вызовы к этим дочерним заглушкам затем будут записаны по порядку в `mock_calls` родительской заглушки:947948```python949>>> manager = Mock()950>>> mock_foo = manager.foo951>>> mock_bar = manager.bar952```953954```python955>>> mock_foo.something()956<Mock name='mock.foo.something()' id='...'>957>>> mock_bar.other.thing()958<Mock name='mock.bar.other.thing()' id='...'>959```960961```python962>>> manager.mock_calls963[call.foo.something(), call.bar.other.thing()]964```965966Затем мы можем делать утверждения о вызовах, включая порядок, сравнивая с атрибутом `mock_calls` на менеджере-заглушке:967968```python969>>> expected_calls = [call.foo.something(), call.bar.other.thing()]970>>> manager.mock_calls == expected_calls971True972```973974Если `patch` создает и устанавливает ваши заглушки, то вы можете прикрепить их к менеджеру-заглушке с помощью метода [`attach_mock()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.attach_mock). После прикрепления вызовы будут записываться в `mock_calls` менеджера.975976```python977>>> manager = MagicMock()978>>> with patch('mymodule.Class1') as MockClass1:979... with patch('mymodule.Class2') as MockClass2:980... manager.attach_mock(MockClass1, 'MockClass1')981... manager.attach_mock(MockClass2, 'MockClass2')982... MockClass1().foo()983... MockClass2().bar()984...985<MagicMock name='mock.MockClass1().foo()' id='...'>986<MagicMock name='mock.MockClass2().bar()' id='...'>987>>> manager.mock_calls988[call.MockClass1(),989 call.MockClass1().foo(),990 call.MockClass2(),991 call.MockClass2().bar()]992```993994Если было сделано много вызовов, но вас интересует только определенная их последовательность, то альтернативой является использование метода [`assert_has_calls()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls). Он принимает список вызовов (созданных с помощью объекта [`call`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.call)). Если эта последовательность вызовов присутствует в [`mock_calls`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.mock_calls), то утверждение проходит успешно.995996```python997>>> m = MagicMock()998>>> m().foo().bar().baz()999<MagicMock name='mock().foo().bar().baz()' id='...'>1000>>> m.one().two().three()1001<MagicMock name='mock.one().two().three()' id='...'>1002>>> calls = call.one().two().three().call_list()1003>>> m.assert_has_calls(calls)1004```10051006Даже если цепочка вызовов `m.one().two().three()` не является единственными вызовами, сделанными к заглушке, утверждение всё равно проходит успешно.10071008Иногда к заглушке может быть сделано несколько вызовов, и вас интересуют только утверждения о *некоторых* из этих вызовов. Вас может даже не волновать порядок. В этом случае можно передать `any_order=True` в `assert_has_calls`:10091010```python1011>>> m = MagicMock()1012>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')1013(...)1014>>> calls = [call.fifty('50'), call(1), call.seven(7)]1015>>> m.assert_has_calls(calls, any_order=True)1016```10171018### 26.5.3.13. Более сложное сопоставление аргументов10191020Используя ту же основную концепцию, что и [`ANY`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.ANY), мы можем реализовать сопоставители для более сложных утверждений об объектах, используемых в качестве аргументов заглушек.10211022Предположим, мы ожидаем, что некоторый объект будет передан заглушке, и этот объект по умолчанию сравнивается по идентичности (что является стандартным для Python для пользовательских классов). Чтобы использовать [`assert_called_with()`](https://python-all.ru/3.4/library/unittest.mock.html#unittest.mock.Mock.assert_called_with), нам нужно передать точно тот же самый объект. Если нас интересуют только некоторые атрибуты этого объекта, то мы можем создать сопоставитель, который будет проверять эти атрибуты за нас.10231024В этом примере можно увидеть, как «стандартный» вызов `assert_called_with` недостаточен:10251026```python1027>>> class Foo:1028... def __init__(self, a, b):1029... self.a, self.b = a, b1030...1031>>> mock = Mock(return_value=None)1032>>> mock(Foo(1, 2))1033>>> mock.assert_called_with(Foo(1, 2))1034Traceback (most recent call last):1035 ...1036AssertionError: Expected: call(<__main__.Foo object at 0x...>)1037Actual call: call(<__main__.Foo object at 0x...>)1038```10391040Функция сравнения для нашего класса `Foo` может выглядеть примерно так:10411042```python1043>>> def compare(self, other):1044... if not type(self) == type(other):1045... return False1046... if self.a != other.a:1047... return False1048... if self.b != other.b:1049... return False1050... return True1051...1052```10531054А объект-сопоставитель, который может использовать такие функции сравнения для операции равенства, будет выглядеть примерно так:10551056```python1057>>> class Matcher:1058... def __init__(self, compare, some_obj):1059... self.compare = compare1060... self.some_obj = some_obj1061... def __eq__(self, other):1062... return self.compare(self.some_obj, other)1063...1064```10651066Собираем всё вместе:10671068```python1069>>> match_foo = Matcher(compare, Foo(1, 2))1070>>> mock.assert_called_with(match_foo)1071```10721073`Matcher` создаётся с нашей функцией сравнения и объектом `Foo`, с которым мы хотим сравнивать. В `assert_called_with` будет вызван метод равенства `Matcher`, который сравнивает объект, с которым была вызвана заглушка, с тем, с которым мы создали наш сопоставитель. Если они совпадают, то `assert_called_with` проходит, а если нет, то возбуждается [`AssertionError`](https://python-all.ru/3.4/library/exceptions.html#AssertionError):10741075```python1076>>> match_wrong = Matcher(compare, Foo(3, 4))1077>>> mock.assert_called_with(match_wrong)1078Traceback (most recent call last):1079 ...1080AssertionError: Expected: ((<Matcher object at 0x...>,), {})1081Called with: ((<Foo object at 0x...>,), {})1082```10831084С помощью небольшой доработки можно заставить функцию сравнения напрямую возбуждать [`AssertionError`](https://python-all.ru/3.4/library/exceptions.html#AssertionError) и предоставлять более полезное сообщение об ошибке.10851086Начиная с версии 1.5, библиотека тестирования Python [PyHamcrest](https://python-all.ru/3.4/library/unittest.mock-examples.html) предоставляет аналогичную функциональность, которая может быть полезна здесь, в виде средства сравнения на равенство ([hamcrest.library.integration.match\_equality](https://python-all.ru/3.4/library/unittest.mock-examples.html)).1087