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

unittest.mock-examples.md

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

1> **Источник:** https://python-all.ru/3.3/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.3/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.3/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.3/library/unittest.mock.html#unittest.mock.Mock) и [`MagicMock`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.MagicMock) взаимозаменяемы. Поскольку *MagicMock* – более функциональный класс, его разумно использовать по умолчанию.3435После вызова mock его атрибут [`called`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.called) устанавливается в *True*. Что более важно, можно использовать метод [`assert_called_with()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.assert_called_with) или [`assert_called_once_with()`](https://python-all.ru/3.3/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.3/library/unittest.mock.html#unittest.mock.Mock.assert_called_with) вызовет исключение.7576### 26.5.1.3. Подмена классов7778Типичный сценарий – подменить классы, которые инстанциируются тестируемым кодом. При подмене класса он заменяется mock. Экземпляры создаются *вызовом класса*. Это значит, что «экземпляр mock» доступен через возвращаемое значение подменённого класса.7980В примере ниже есть функция *some\_function*, которая создаёт экземпляр *Foo* и вызывает его метод. Вызов *patch* заменяет класс *Foo* на mock. Экземпляр *Foo* – это результат вызова mock, поэтому он настраивается через изменение [`return_value`](https://python-all.ru/3.3/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.3/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Для создания списков для сравнения с *mock\_calls* используется объект [`call`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.call):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.3/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()* превращает наш объект вызова в список вызовов, представляющих цепочку вызовов.185186### 26.5.1.7. Вызов исключений с помощью заглушек187188Полезный атрибут – [`side_effect`](https://python-all.ru/3.3/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* позволяет предоставить объект в качестве спецификации для 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Если нужна более строгая форма спецификации, запрещающая как установку произвольных атрибутов, так и их получение, можно использовать *spec\_set* вместо *spec*.241242## 26.5.2. Декораторы patch243244> **Примечание**245>246> При использовании *patch* важно подменять объекты в том пространстве имён, где они ищутся. Обычно это очевидно, но для краткого руководства прочитайте [*где подменять*](https://python-all.ru/3.3/library/unittest.mock.html#where-to-patch).247248Частая потребность в тестах – подменить атрибут класса или модуля, например, встроенную функцию или класс в модуле, чтобы проверить, что он инстанциируется. Модули и классы по сути глобальны, поэтому подмену на них нужно отменять после теста, иначе она останется и в других тестах, вызывая труднодиагностируемые проблемы.249250mock предоставляет три удобных декоратора для этого: *patch*, *patch.object* и *patch.dict*. *patch* принимает одну строку вида *package.module.Class.attribute*, чтобы указать атрибут, который нужно подменить. Также он опционально принимает значение, на которое нужно заменить атрибут (или класс и т.д.). ‘patch.object’ принимает объект и имя атрибута, который нужно подменить, плюс опционально значение для подмены.251252*patch.object*:253254```python255>>> original = SomeClass.attribute256>>> @patch.object(SomeClass, 'attribute', sentinel.attribute)257... def test():258...     assert SomeClass.attribute == sentinel.attribute259...260>>> test()261>>> assert SomeClass.attribute == original262```263264```python265>>> @patch('package.module.attribute', sentinel.attribute)266... def test():267...     from package.module import attribute268...     assert attribute is sentinel.attribute269...270>>> test()271```272273Если вы подменяете модуль (включая [`builtins`](https://python-all.ru/3.3/library/builtins.html#module-builtins)), то используйте *patch* вместо *patch.object*:274275```python276>>> mock = MagicMock(return_value=sentinel.file_handle)277>>> with patch('builtins.open', mock):278...     handle = open('filename', 'r')279...280>>> mock.assert_called_with('filename', 'r')281>>> assert handle == sentinel.file_handle, "incorrect file handle returned"282```283284Имя модуля может быть точечным, в форме *package.module*, если необходимо:285286```python287>>> @patch('package.module.ClassName.attribute', sentinel.attribute)288... def test():289...     from package.module import ClassName290...     assert ClassName.attribute == sentinel.attribute291...292>>> test()293```294295Удобный подход – декорировать непосредственно сами тестовые методы:296297```python298>>> class MyTest(unittest2.TestCase):299...     @patch.object(SomeClass, 'attribute', sentinel.attribute)300...     def test_something(self):301...         self.assertEqual(SomeClass.attribute, sentinel.attribute)302...303>>> original = SomeClass.attribute304>>> MyTest('test_something').test_something()305>>> assert SomeClass.attribute == original306```307308Если нужно подменить с помощью Mock, можно использовать *patch* с одним аргументом (или *patch.object* с двумя аргументами). Mock будет создан автоматически и передан в тестовую функцию/метод:309310```python311>>> class MyTest(unittest2.TestCase):312...     @patch.object(SomeClass, 'static_method')313...     def test_something(self, mock_method):314...         SomeClass.static_method()315...         mock_method.assert_called_with()316...317>>> MyTest('test_something').test_something()318```319320Можно накладывать несколько декораторов патча, используя такой шаблон:321322```python323>>> class MyTest(unittest2.TestCase):324...     @patch('package.module.ClassName1')325...     @patch('package.module.ClassName2')326...     def test_something(self, MockClass2, MockClass1):327...         self.assertIs(package.module.ClassName1, MockClass1)328...         self.assertIs(package.module.ClassName2, MockClass2)329...330>>> MyTest('test_something').test_something()331```332333При вложении декораторов patch mocks передаются в декорированную функцию в том же порядке, в каком они применяются (обычный *python* порядок применения декораторов). То есть снизу вверх, поэтому в примере выше mock для *test\_module.ClassName2* передаётся первым.334335Существует также [`patch.dict()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.patch.dict) для установки значений в словаре только в пределах области видимости с последующим восстановлением исходного состояния словаря после завершения теста:336337```python338>>> foo = {'key': 'value'}339>>> original = foo.copy()340>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):341...     assert foo == {'newkey': 'newvalue'}342...343>>> assert foo == original344```345346*patch*, *patch.object* и *patch.dict* можно использовать как менеджеры контекста.347348Если используется *patch* для создания mock, можно получить ссылку на mock с помощью формы “as” инструкции with:349350```python351>>> class ProductionClass:352...     def method(self):353...         pass354...355>>> with patch.object(ProductionClass, 'method') as mock_method:356...     mock_method.return_value = None357...     real = ProductionClass()358...     real.method(1, 2, 3)359...360>>> mock_method.assert_called_with(1, 2, 3)361```362363В качестве альтернативы *patch*, *patch.object* и *patch.dict* можно использовать как декораторы классов. При таком использовании это равносильно применению декоратора по отдельности к каждому методу, имя которого начинается с “test”.364365## 26.5.3. Дополнительные примеры366367Вот ещё несколько примеров для немного более сложных сценариев.368369### 26.5.3.1. Мокирование цепочечных вызовов370371Мокирование цепочек вызовов на самом деле просто с mock, если понять атрибут [`return_value`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.return_value). Когда mock вызывается в первый раз или вы получаете его *return\_value* до вызова, создаётся новый *Mock*.372373Это означает, что можно увидеть, как был использован объект, возвращённый вызовом mock-объекта, запросив *return\_value* mock:374375```python376>>> mock = Mock()377>>> mock().foo(a=2, b=3)378<Mock name='mock().foo()' id='...'>379>>> mock.return_value.foo.assert_called_with(a=2, b=3)380```381382Отсюда всего один шаг до настройки и последующих проверок цепочечных вызовов. Конечно, другой альтернативой является написание кода сразу в более тестируемом виде...383384Итак, предположим, у нас есть код, который выглядит примерно так:385386```python387>>> class Something:388...     def __init__(self):389...         self.backend = BackendProvider()390...     def method(self):391...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()392...         # ещё код393```394395Предполагая, что *BackendProvider* уже хорошо протестирован, как тестировать *method()*? В частности, нужно проверить, что участок кода *# more code* использует объект ответа правильным образом.396397Поскольку эта цепочка вызовов делается через атрибут экземпляра, можно сделать monkey patch атрибута *backend* у экземпляра *Something*. В данном конкретном случае нас интересует только возвращаемое значение последнего вызова *start\_call*, поэтому настройки почти не требуется. Предположим, что возвращаемый объект является файлоподобным, поэтому обеспечим, чтобы наш объект ответа использовал встроенный *open* в качестве своего *spec*.398399Для этого создадим mock-экземпляр в качестве mock-бэкенда и создадим mock-объект ответа для него. Чтобы задать ответ как возвращаемое значение для этого последнего *start\_call*, можно сделать так:400401> *mock\_backend.get\_endpoint.return\_value.create\_call.return\_value.start\_call.return\_value = mock\_response*402>403> .404405Это можно сделать немного элегантнее с помощью метода [`configure_mock()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.configure_mock), чтобы напрямую установить возвращаемое значение:406407```python408>>> something = Something()409>>> mock_response = Mock(spec=open)410>>> mock_backend = Mock()411>>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}412>>> mock_backend.configure_mock(**config)413```414415С их помощью мы применяем monkey-patch к «mock backend» и можем выполнить реальный вызов:416417```python418>>> something.backend = mock_backend419>>> something.method()420```421422Используя [`mock_calls`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.mock_calls), можно проверить цепочку вызовов одним assert. Цепочка вызовов – это несколько вызовов в одной строке кода, поэтому в *mock\_calls* будет несколько записей. Можно использовать [`call.call_list()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.call.call_list), чтобы создать этот список вызовов для нас:423424```python425>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()426>>> call_list = chained.call_list()427>>> assert mock_backend.mock_calls == call_list428```429430### 26.5.3.2. Частичное мокирование431432В некоторых тестах требовалось подменить вызов [datetime.date.today()](https://python-all.ru/3.3/library/datetime.html#datetime.date.today), чтобы он возвращал известную дату, но при этом не мешать тестируемому коду создавать новые объекты даты. К сожалению, *datetime.date* написан на C, и невозможно было просто подменить статический метод *date.today* через монки-патчинг.433434Я нашел простой способ сделать это: обернуть класс date в mock, но передавать вызовы конструктора настоящему классу (и возвращать настоящие экземпляры).435436Здесь [`patch декоратор`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.patch) используется для подмены класса *date* в тестируемом модуле. Затем атрибуту `side_effect` у поддельного класса date присваивается лямбда-функция, возвращающая настоящую дату. При вызове поддельного класса date будет создана и возвращена реальная дата через *side\_effect*.437438```python439>>> from datetime import date440>>> with patch('mymodule.date') as mock_date:441...     mock_date.today.return_value = date(2010, 10, 8)442...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)443...444...     assert mymodule.date.today() == date(2010, 10, 8)445...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)446...447```448449Обратите внимание, что мы не подменяем *datetime.date* глобально; мы подменяем *date* в том модуле, который его *использует*. См. [*где подменять*](https://python-all.ru/3.3/library/unittest.mock.html#where-to-patch).450451Когда вызывается *date.today()*, возвращается известная дата, но вызовы конструктора *date(...)* по-прежнему возвращают обычные даты. Без этого можно столкнуться с необходимостью вычислять ожидаемый результат с помощью того же алгоритма, что и в тестируемом коде, что является классическим антипаттерном тестирования.452453Вызовы конструктора date записываются в атрибуты *mock\_date* (*call\_count* и другие), что также может быть полезно для ваших тестов.454455Альтернативный способ мокирования дат или других встроенных классов описан в [этой записи блога](https://python-all.ru/3.3/library/unittest.mock-examples.html).456457### 26.5.3.3. Мокирование метода-генератора458459Генератор Python – это функция или метод, которая использует оператор [yield statement](https://python-all.ru/3.3/reference/simple_stmts.html#the-yield-statement) для возврата последовательности значений при итерации [\[1\]](https://python-all.ru/3.3/library/unittest.mock-examples.html#id3).460461Метод или функция генератора вызывается для возврата объекта генератора. Затем выполняется итерация именно по этому объекту генератора. Метод протокола для итерации – [\_\_iter\_\_](https://python-all.ru/3.3/library/stdtypes.html#container.__iter__), поэтому можно подменить это с помощью *MagicMock*.462463Вот пример класса с методом “iter”, реализованным как генератор:464465```python466>>> class Foo:467...     def iter(self):468...         for i in [1, 2, 3]:469...             yield i470...471>>> foo = Foo()472>>> list(foo.iter())473[1, 2, 3]474```475476Как бы мы мокировали этот класс и, в частности, его метод “iter”?477478Чтобы настроить значения, возвращаемые при итерации (неявно при вызове *list*), необходимо настроить объект, возвращаемый вызовом *foo.iter()*.479480```python481>>> mock_foo = MagicMock()482>>> mock_foo.iter.return_value = iter([1, 2, 3])483>>> list(mock_foo.iter())484[1, 2, 3]485```486487| [\[1\]](https://python-all.ru/3.3/library/unittest.mock-examples.html#id2) | Существуют также генераторные выражения и более [продвинутые применения](https://python-all.ru/3.3/library/unittest.mock-examples.html) генераторов, но здесь они нас не интересуют. Очень хорошее введение в генераторы и их мощь: [Generator Tricks for Systems Programmers](https://python-all.ru/3.3/library/unittest.mock-examples.html). |488| --- | --- |489490### 26.5.3.4. Применение одного и того же патча к каждому тестовому методу491492Если нужно настроить несколько подмен для нескольких тестовых методов, очевидный способ – применить декораторы patch к каждому методу. Это может показаться излишним повторением. Начиная с Python 2.6 можно использовать *patch* (во всех его формах) как декоратор класса. Это применяет подмены ко всем тестовым методам класса. Тестовый метод определяется как метод, имя которого начинается с *test*:493494```python495>>> @patch('mymodule.SomeClass')496... class MyTest(TestCase):497...498...     def test_one(self, MockSomeClass):499...         self.assertIs(mymodule.SomeClass, MockSomeClass)500...501...     def test_two(self, MockSomeClass):502...         self.assertIs(mymodule.SomeClass, MockSomeClass)503...504...     def not_a_test(self):505...         return 'something'506...507>>> MyTest('test_one').test_one()508>>> MyTest('test_two').test_two()509>>> MyTest('test_two').not_a_test()510'something'511```512513Альтернативный способ управления подменами – использование [*методов patch: start и stop*](https://python-all.ru/3.3/library/unittest.mock.html#start-and-stop). Они позволяют перенести подмену в методы *setUp* и *tearDown*.514515```python516>>> class MyTest(TestCase):517...     def setUp(self):518...         self.patcher = patch('mymodule.foo')519...         self.mock_foo = self.patcher.start()520...521...     def test_foo(self):522...         self.assertIs(mymodule.foo, self.mock_foo)523...524...     def tearDown(self):525...         self.patcher.stop()526...527>>> MyTest('test_foo').run()528```529530Если используется этот приём, необходимо убедиться, что подмена «отменена» вызовом *stop*. Это может быть сложнее, чем кажется, потому что если в setUp возникло исключение, tearDown не вызывается. [`unittest.TestCase.addCleanup()`](https://python-all.ru/3.3/library/unittest.html#unittest.TestCase.addCleanup) упрощает это:531532```python533>>> class MyTest(TestCase):534...     def setUp(self):535...         patcher = patch('mymodule.foo')536...         self.addCleanup(patcher.stop)537...         self.mock_foo = patcher.start()538...539...     def test_foo(self):540...         self.assertIs(mymodule.foo, self.mock_foo)541...542>>> MyTest('test_foo').run()543```544545### 26.5.3.5. Мокирование несвязанных методов546547При написании тестов мне потребовалось исправить *несвязанный метод* (исправить метод на уровне класса, а не экземпляра). Мне нужно было, чтобы self передавался первым аргументом, потому что я хотел проверять, какие объекты вызывали этот конкретный метод. Проблема в том, что для этого нельзя использовать мок: если заменить несвязанный метод на мок, тот не станет связанным методом при получении из экземпляра, и self не будет передан. Обходной путь – исправить несвязанный метод настоящей функцией. Декоратор [`patch()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.patch) настолько упрощает замену методов моками, что создание настоящей функции становится обременительным.548549Если передать *autospec=True* в patch, то подмена выполняется с помощью *настоящего* объекта функции. Этот объект функции имеет ту же сигнатуру, что и заменяемый, но внутри делегирует вызовы mock. При этом mock по-прежнему создаётся автоматически так же, как и раньше. Однако это означает, что если использовать его для подмены непривязанного метода класса, то подменённая функция станет привязанным методом, если получить её из экземпляра. В неё будет передан *self* в качестве первого аргумента, что именно и требовалось:550551```python552>>> class Foo:553...   def foo(self):554...     pass555...556>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:557...   mock_foo.return_value = 'foo'558...   foo = Foo()559...   foo.foo()560...561'foo'562>>> mock_foo.assert_called_once_with(foo)563```564565Если не использовать *autospec=True*, то непривязанный метод подменяется экземпляром Mock, и он не вызывается с *self*.566567### 26.5.3.6. Проверка множественных вызовов с помощью mock568569У mock есть удобный API для проверки того, как используются объекты mock.570571```python572>>> mock = Mock()573>>> mock.foo_bar.return_value = None574>>> mock.foo_bar('baz', spam='eggs')575>>> mock.foo_bar.assert_called_with('baz', spam='eggs')576```577578Если мок вызывается только один раз, можно использовать метод `assert_called_once_with()`, который также проверяет, что `call_count` равен единице.579580```python581>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')582>>> mock.foo_bar()583>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')584Traceback (most recent call last):585    ...586AssertionError: Expected to be called once. Called 2 times.587```588589Оба метода *assert\_called\_with* и *assert\_called\_once\_with* делают утверждения о *последнем* вызове. Если mock будет вызван несколько раз, и нужно сделать утверждения обо *всех* этих вызовах, можно использовать [`call_args_list`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.call_args_list):590591```python592>>> mock = Mock(return_value=None)593>>> mock(1, 2, 3)594>>> mock(4, 5, 6)595>>> mock()596>>> mock.call_args_list597[call(1, 2, 3), call(4, 5, 6), call()]598```599600Вспомогательный объект [`call`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.call) упрощает создание утверждений об этих вызовах. Можно составить список ожидаемых вызовов и сравнить его с *call\_args\_list*. Это выглядит очень похоже на строковое представление *call\_args\_list*:601602```python603>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]604>>> mock.call_args_list == expected605True606```607608### 26.5.3.7. Работа с изменяемыми аргументами609610Ещё одна редкая, но коварная ситуация – когда mock вызывается с изменяемыми аргументами. *call\_args* и *call\_args\_list* хранят *ссылки* на аргументы. Если аргументы изменяются тестируемым кодом, то уже невозможно сделать утверждения о том, какие значения были на момент вызова mock.611612Вот пример кода, демонстрирующий проблему. Предположим, в 'mymodule' определены следующие функции:613614```python615def frob(val):616    pass617618def grob(val):619    "First frob and then clear val"620    frob(val)621    val.clear()622```623624Когда мы пытаемся проверить, что *grob* вызывает *frob* с правильным аргументом, посмотрите, что происходит:625626```python627>>> with patch('mymodule.frob') as mock_frob:628...     val = set([6])629...     mymodule.grob(val)630...631>>> val632set([])633>>> mock_frob.assert_called_with(set([6]))634Traceback (most recent call last):635    ...636AssertionError: Expected: ((set([6]),), {})637Called with: ((set([]),), {})638```639640Одним из возможных решений было бы копирование аргументов, передаваемых в mock. Однако это может вызвать проблемы, если проверки полагаются на тождественность объектов для сравнения.641642Here’s one solution that uses the `side_effect` functionality. If you provide a *side\_effect* function for a mock then *side\_effect* will be called with the same args as the mock. This gives us an opportunity to copy the arguments and store them for later assertions. In this example I’m using *another* mock to store the arguments so that I can use the mock methods for doing the assertion. Again a helper function sets this up for me.643644```python645>>> from copy import deepcopy646>>> from unittest.mock import Mock, patch, DEFAULT647>>> def copy_call_args(mock):648...     new_mock = Mock()649...     def side_effect(*args, **kwargs):650...         args = deepcopy(args)651...         kwargs = deepcopy(kwargs)652...         new_mock(*args, **kwargs)653...         return DEFAULT654...     mock.side_effect = side_effect655...     return new_mock656...657>>> with patch('mymodule.frob') as mock_frob:658...     new_mock = copy_call_args(mock_frob)659...     val = set([6])660...     mymodule.grob(val)661...662>>> new_mock.assert_called_with(set([6]))663>>> new_mock.call_args664call(set([6]))665```666667*copy\_call\_args* вызывается с mock, который будет вызван. Она возвращает новый mock, на котором мы делаем утверждения. Функция *side\_effect* создаёт копию аргументов и вызывает *new\_mock* с этой копией.668669> **Примечание**670>671> Если mock будет использован только один раз, есть более простой способ проверки аргументов в момент вызова. Можно просто выполнить проверку внутри функции *side\_effect*.672>673> ```python674> >>> def side_effect(arg):675> ...     assert arg == set([6])676> ...677> >>> mock = Mock(side_effect=side_effect)678> >>> mock(set([6]))679> >>> mock(set())680> Traceback (most recent call last):681>     ...682> AssertionError683> ```684685Альтернативный подход – создать подкласс *Mock* или *MagicMock*, который копирует аргументы (с помощью [`copy.deepcopy()`](https://python-all.ru/3.3/library/copy.html#copy.deepcopy)). Вот пример реализации:686687```python688>>> from copy import deepcopy689>>> class CopyingMock(MagicMock):690...     def __call__(self, *args, **kwargs):691...         args = deepcopy(args)692...         kwargs = deepcopy(kwargs)693...         return super(CopyingMock, self).__call__(*args, **kwargs)694...695>>> c = CopyingMock(return_value=None)696>>> arg = set()697>>> c(arg)698>>> arg.add(1)699>>> c.assert_called_with(set())700>>> c.assert_called_with(arg)701Traceback (most recent call last):702    ...703AssertionError: Expected call: mock(set([1]))704Actual call: mock(set([]))705>>> c.foo706<CopyingMock name='mock.foo' id='...'>707```708709При создании подкласса *Mock* или *MagicMock* все динамически создаваемые атрибуты и *return\_value* будут автоматически использовать ваш подкласс. Это означает, что все дочерние объекты *CopyingMock* также будут иметь тип *CopyingMock*.710711### 26.5.3.8. Вложение патчей712713Использовать patch как менеджер контекста удобно, но при множественных подстановках можно получить вложенные операторы with, уходящие всё дальше вправо с отступами:714715```python716>>> class MyTest(TestCase):717...718...     def test_foo(self):719...         with patch('mymodule.Foo') as mock_foo:720...             with patch('mymodule.Bar') as mock_bar:721...                 with patch('mymodule.Spam') as mock_spam:722...                     assert mymodule.Foo is mock_foo723...                     assert mymodule.Bar is mock_bar724...                     assert mymodule.Spam is mock_spam725...726>>> original = mymodule.Foo727>>> MyTest('test_foo').test_foo()728>>> assert mymodule.Foo is original729```730731С помощью функций очистки *cleanup* из unittest и [*методов patch: start и stop*](https://python-all.ru/3.3/library/unittest.mock.html#start-and-stop) можно добиться того же эффекта без вложенных отступов. Простой вспомогательный метод *create\_patch* устанавливает подмену и возвращает созданный mock:732733```python734>>> class MyTest(TestCase):735...736...     def create_patch(self, name):737...         patcher = patch(name)738...         thing = patcher.start()739...         self.addCleanup(patcher.stop)740...         return thing741...742...     def test_foo(self):743...         mock_foo = self.create_patch('mymodule.Foo')744...         mock_bar = self.create_patch('mymodule.Bar')745...         mock_spam = self.create_patch('mymodule.Spam')746...747...         assert mymodule.Foo is mock_foo748...         assert mymodule.Bar is mock_bar749...         assert mymodule.Spam is mock_spam750...751>>> original = mymodule.Foo752>>> MyTest('test_foo').run()753>>> assert mymodule.Foo is original754```755756### 26.5.3.9. Мокирование словаря с помощью MagicMock757758Может потребоваться заменить словарь (или другой контейнер) mock'ом, записывающим все обращения к нему, но при этом сохраняющим поведение словаря.759760Это можно сделать с помощью [`MagicMock`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.MagicMock), который будет вести себя как словарь, используя [`side_effect`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.side_effect) для делегирования доступа к словарю реальному словарю под нашим контролем.761762Когда вызываются методы *\_\_getitem\_\_* и *\_\_setitem\_\_* нашего *MagicMock* (обычный доступ к словарю), вызывается *side\_effect* с ключом (а в случае *\_\_setitem\_\_* ещё и со значением). Также можно управлять возвращаемым значением.763764После использования *MagicMock* можно использовать такие атрибуты, как [`call_args_list`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.call_args_list), чтобы проверить, как использовался словарь:765766```python767>>> my_dict = {'a': 1, 'b': 2, 'c': 3}768>>> def getitem(name):769...      return my_dict[name]770...771>>> def setitem(name, val):772...     my_dict[name] = val773...774>>> mock = MagicMock()775>>> mock.__getitem__.side_effect = getitem776>>> mock.__setitem__.side_effect = setitem777```778779> **Примечание**780>781> Альтернатива использованию *MagicMock* – использовать *Mock* и *только* предоставить те магические методы, которые нужны:782>783> ```python784> >>> mock = Mock()785> >>> mock.__setitem__ = Mock(side_effect=getitem)786> >>> mock.__getitem__ = Mock(side_effect=setitem)787> ```788>789> *Третий* вариант – использовать *MagicMock*, но передав *dict* в качестве аргумента *spec* (или *spec\_set*), чтобы созданный *MagicMock* имел только магические методы словаря:790>791> ```python792> >>> mock = MagicMock(spec_set=dict)793> >>> mock.__getitem__.side_effect = getitem794> >>> mock.__setitem__.side_effect = setitem795> ```796797При наличии этих функций side\_effect *mock* будет вести себя как обычный словарь, но записывая обращения. Он даже возбуждает *KeyError* при попытке доступа к несуществующему ключу.798799```python800>>> mock['a']8011802>>> mock['c']8033804>>> mock['d']805Traceback (most recent call last):806    ...807KeyError: 'd'808>>> mock['b'] = 'fish'809>>> mock['d'] = 'eggs'810>>> mock['b']811'fish'812>>> mock['d']813'eggs'814```815816После использования можно делать утверждения о доступе, используя обычные методы и атрибуты mock:817818```python819>>> mock.__getitem__.call_args_list820[call('a'), call('c'), call('d'), call('b'), call('d')]821>>> mock.__setitem__.call_args_list822[call('b', 'fish'), call('d', 'eggs')]823>>> my_dict824{'a': 1, 'c': 3, 'b': 'fish', 'd': 'eggs'}825```826827### 26.5.3.10. Подклассы Mock и их атрибуты828829Существует множество причин для создания подкласса *Mock*. Одна из причин – добавление вспомогательных методов. Вот забавный пример:830831```python832>>> class MyMock(MagicMock):833...     def has_been_called(self):834...         return self.called835...836>>> mymock = MyMock(return_value=None)837>>> mymock838<MyMock id='...'>839>>> mymock.has_been_called()840False841>>> mymock()842>>> mymock.has_been_called()843True844```845846Стандартное поведение для экземпляров *Mock* заключается в том, что атрибуты и возвращаемые значения mock имеют тот же тип, что и mock, к которому они обращаются. Это гарантирует, что атрибуты *Mock* являются *Mock*, а атрибуты *MagicMock* являются *MagicMock* [\[2\]](https://python-all.ru/3.3/library/unittest.mock-examples.html#id5). Поэтому если создаётся подкласс для добавления вспомогательных методов, они также будут доступны для атрибутов и возвращаемых значений mock экземпляров вашего подкласса.847848```python849>>> mymock.foo850<MyMock name='mock.foo' id='...'>851>>> mymock.foo.has_been_called()852False853>>> mymock.foo()854<MyMock name='mock.foo()' id='...'>855>>> mymock.foo.has_been_called()856True857```858859Иногда это неудобно. Например, [один пользователь](https://python-all.ru/3.3/library/unittest.mock-examples.html) создаёт подкласс mock для [адаптера Twisted](https://python-all.ru/3.3/library/unittest.mock-examples.html). Применение этого же поведения к атрибутам приводит к ошибкам.860861*Mock* (во всех своих вариантах) использует метод *\_get\_child\_mock* для создания этих «дочерних mock» для атрибутов и возвращаемых значений. Можно предотвратить использование подкласса для атрибутов, переопределив этот метод. Его сигнатура: он принимает произвольные именованные аргументы (*\*\*kwargs*), которые затем передаются конструктору mock:862863```python864>>> class Subclass(MagicMock):865...     def _get_child_mock(self, **kwargs):866...         return MagicMock(**kwargs)867...868>>> mymock = Subclass()869>>> mymock.foo870<MagicMock name='mock.foo' id='...'>871>>> assert isinstance(mymock, Subclass)872>>> assert not isinstance(mymock.foo, Subclass)873>>> assert not isinstance(mymock(), Subclass)874```875876| [\[2\]](https://python-all.ru/3.3/library/unittest.mock-examples.html#id4) | Исключение из этого правила – невызываемые имитации. Атрибуты используют вызываемый вариант, потому что иначе невызываемые имитации не могли бы иметь вызываемые методы. |877| --- | --- |878879### 26.5.3.11. Имитация импорта с помощью patch.dict880881Одна из ситуаций, где имитация сложна – локальный импорт внутри функции. Такие импорты труднее подменить, потому что они не используют объект из пространства имён модуля, который можно замокать.882883В целом локальных импортов следует избегать. Иногда их используют для предотвращения циклических зависимостей, для чего *обычно* есть гораздо лучшее решение (рефакторинг кода), или для снижения «начальных затрат» путём отложенного импорта. Эту проблему также можно решить лучше, чем безусловный локальный импорт (сохранить модуль как атрибут класса или модуля и выполнять импорт только при первом использовании).884885Помимо этого, есть способ использовать *mock*, чтобы влиять на результаты импорта. Импорт извлекает *объект* из словаря *sys.modules*. Обратите внимание, что извлекается *объект*, который не обязательно является модулем. При первом импорте модуля в *sys.modules* помещается объект модуля, поэтому обычно при импорте чего-либо вы получаете модуль. Однако это не обязательно так.886887Это значит, что вы можете использовать [`patch.dict()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.patch.dict), чтобы *временно* поместить мок в *sys.modules*. Любой импорт, пока активен этот патч, будет извлекать мок. Когда патч завершается (декорированная функция завершается, тело with-блока заканчивается или вызывается *patcher.stop()*), то все, что было ранее, безопасно восстанавливается.888889Вот пример, который имитирует модуль 'fooble'.890891```python892>>> mock = Mock()893>>> with patch.dict('sys.modules', {'fooble': mock}):894...    import fooble895...    fooble.blob()896...897<Mock name='mock.blob()' id='...'>898>>> assert 'fooble' not in sys.modules899>>> mock.blob.assert_called_once_with()900```901902Как видите, *import fooble* выполняется успешно, но после выхода в *sys.modules* не остаётся 'fooble'.903904Это также работает для формы *from module import name*:905906```python907>>> mock = Mock()908>>> with patch.dict('sys.modules', {'fooble': mock}):909...    from fooble import blob910...    blob.blip()911...912<Mock name='mock.blob.blip()' id='...'>913>>> mock.blob.blip.assert_called_once_with()914```915916Немного больше усилий, и можно также имитировать импорт пакетов:917918```python919>>> mock = Mock()920>>> modules = {'package': mock, 'package.module': mock.module}921>>> with patch.dict('sys.modules', modules):922...    from package.module import fooble923...    fooble()924...925<Mock name='mock.module.fooble()' id='...'>926>>> mock.module.fooble.assert_called_once_with()927```928929### 26.5.3.12. Отслеживание порядка вызовов и менее многословные утверждения о вызовах930931Класс [`Mock`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock) позволяет отслеживать *порядок* вызовов методов для объектов-заглушек через атрибут [`method_calls`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.method_calls). Это не позволяет отслеживать порядок вызовов между отдельными объектами-заглушками, однако мы можем использовать [`mock_calls`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.mock_calls) для достижения того же эффекта.932933Поскольку моки отслеживают вызовы дочерних моков в *mock\_calls*, и обращение к произвольному атрибуту мока создает дочерний мок, мы можем создавать отдельные моки из родительского. Затем все вызовы этих дочерних моков будут записаны по порядку в *mock\_calls* родителя:934935```python936>>> manager = Mock()937>>> mock_foo = manager.foo938>>> mock_bar = manager.bar939```940941```python942>>> mock_foo.something()943<Mock name='mock.foo.something()' id='...'>944>>> mock_bar.other.thing()945<Mock name='mock.bar.other.thing()' id='...'>946```947948```python949>>> manager.mock_calls950[call.foo.something(), call.bar.other.thing()]951```952953Затем мы можем проверять утверждения о вызовах, включая порядок, сравнивая с атрибутом *mock\_calls* у мок-менеджера:954955```python956>>> expected_calls = [call.foo.something(), call.bar.other.thing()]957>>> manager.mock_calls == expected_calls958True959```960961Если *patch* создает и размещает ваши моки, то вы можете прикрепить их к мок-менеджеру с помощью метода [`attach_mock()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.attach_mock). После прикрепления вызовы будут записываться в *mock\_calls* менеджера.962963```python964>>> manager = MagicMock()965>>> with patch('mymodule.Class1') as MockClass1:966...     with patch('mymodule.Class2') as MockClass2:967...         manager.attach_mock(MockClass1, 'MockClass1')968...         manager.attach_mock(MockClass2, 'MockClass2')969...         MockClass1().foo()970...         MockClass2().bar()971...972<MagicMock name='mock.MockClass1().foo()' id='...'>973<MagicMock name='mock.MockClass2().bar()' id='...'>974>>> manager.mock_calls975[call.MockClass1(),976 call.MockClass1().foo(),977 call.MockClass2(),978 call.MockClass2().bar()]979```980981Если было сделано много вызовов, но вас интересует только определенная их последовательность, то альтернативой является использование метода [`assert_has_calls()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls). Он принимает список вызовов (созданных с помощью объекта [`call`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.call)). Если эта последовательность вызовов присутствует в [`mock_calls`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.mock_calls), то утверждение проходит успешно.982983```python984>>> m = MagicMock()985>>> m().foo().bar().baz()986<MagicMock name='mock().foo().bar().baz()' id='...'>987>>> m.one().two().three()988<MagicMock name='mock.one().two().three()' id='...'>989>>> calls = call.one().two().three().call_list()990>>> m.assert_has_calls(calls)991```992993Даже если цепочка вызовов *m.one().two().three()* – не единственные вызовы, которые были сделаны к моку, утверждение всё равно проходит.994995Иногда мок может иметь несколько вызовов, и вас интересует проверка только *некоторых* из них. Вас может даже не волновать порядок. В этом случае можно передать *any\_order=True* в *assert\_has\_calls*:996997```python998>>> m = MagicMock()999>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')1000(...)1001>>> calls = [call.fifty('50'), call(1), call.seven(7)]1002>>> m.assert_has_calls(calls, any_order=True)1003```10041005### 26.5.3.13. Более сложное сопоставление аргументов10061007Используя ту же основную концепцию, что и [`ANY`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.ANY), мы можем реализовать сопоставители для более сложных утверждений об объектах, используемых в качестве аргументов заглушек.10081009Предположим, мы ожидаем, что некоторый объект будет передан заглушке, и этот объект по умолчанию сравнивается по идентичности (что является стандартным для Python для пользовательских классов). Чтобы использовать [`assert_called_with()`](https://python-all.ru/3.3/library/unittest.mock.html#unittest.mock.Mock.assert_called_with), нам нужно передать точно тот же самый объект. Если нас интересуют только некоторые атрибуты этого объекта, то мы можем создать сопоставитель, который будет проверять эти атрибуты за нас.10101011В этом примере видно, как «стандартный» вызов *assert\_called\_with* не является достаточным:10121013```python1014>>> class Foo:1015...     def __init__(self, a, b):1016...         self.a, self.b = a, b1017...1018>>> mock = Mock(return_value=None)1019>>> mock(Foo(1, 2))1020>>> mock.assert_called_with(Foo(1, 2))1021Traceback (most recent call last):1022    ...1023AssertionError: Expected: call(<__main__.Foo object at 0x...>)1024Actual call: call(<__main__.Foo object at 0x...>)1025```10261027Функция сравнения для нашего класса *Foo* может выглядеть примерно так:10281029```python1030>>> def compare(self, other):1031...     if not type(self) == type(other):1032...         return False1033...     if self.a != other.a:1034...         return False1035...     if self.b != other.b:1036...         return False1037...     return True1038...1039```10401041А объект-сопоставитель, который может использовать такие функции сравнения для операции равенства, будет выглядеть примерно так:10421043```python1044>>> class Matcher:1045...     def __init__(self, compare, some_obj):1046...         self.compare = compare1047...         self.some_obj = some_obj1048...     def __eq__(self, other):1049...         return self.compare(self.some_obj, other)1050...1051```10521053Собираем всё вместе:10541055```python1056>>> match_foo = Matcher(compare, Foo(1, 2))1057>>> mock.assert_called_with(match_foo)1058```10591060*Matcher* создается с нашей функцией сравнения и объектом *Foo*, с которым мы хотим сравнивать. В *assert\_called\_with* будет вызван метод равенства *Matcher*, который сравнивает объект, с которым был вызван мок, с тем, с которым мы создали наш матчер. Если они совпадают, то *assert\_called\_with* проходит, а если нет – вызывается *AssertionError*:10611062```python1063>>> match_wrong = Matcher(compare, Foo(3, 4))1064>>> mock.assert_called_with(match_wrong)1065Traceback (most recent call last):1066    ...1067AssertionError: Expected: ((<Matcher object at 0x...>,), {})1068Called with: ((<Foo object at 0x...>,), {})1069```10701071Немного доработав, можно заставить функцию сравнения вызывать *AssertionError* напрямую и предоставлять более полезное сообщение об ошибке.10721073Начиная с версии 1.5, библиотека тестирования Python [PyHamcrest](https://python-all.ru/3.3/library/unittest.mock-examples.html) предоставляет аналогичную функциональность, которая может быть полезна здесь, в виде средства сравнения на равенство ([hamcrest.library.integration.match\_equality](https://python-all.ru/3.3/library/unittest.mock-examples.html)).1074