# `unittest.mock` — getting started

Нове в версії 3.3.

## Використання Mock

### Імітаційні методи виправлення

Загальні випадки використання об’єктів `Mock` включають:

- Методи латання

- Виклики методів запису на об’єктах

Можливо, ви захочете замінити метод на об’єкті, щоб перевірити, чи викликається він із правильними аргументами іншою частиною системи:

    >>> real = SomeClass()
    >>> real.method = MagicMock(name='method')
    >>> real.method(3, 4, 5, key='value')
    <MagicMock name='method()' id='...'>

Після того, як наш макет був використаний (`real.method` у цьому прикладі), він має методи та атрибути, які дозволяють робити твердження про те, як він використовувався.

Примітка

У більшості цих прикладів класи `Mock` і `MagicMock` взаємозамінні. Оскільки `MagicMock` є більш потужним класом, його доцільно використовувати за замовчуванням.

Після виклику макета його атрибут `called` має значення `True`. Що ще важливіше, ми можемо використовувати метод `assert_ called_with()` або `assert_ called_once_with()`, щоб перевірити, чи його було викликано з правильними аргументами.

Цей приклад перевіряє, що виклик `ProductionClass().method` призводить до виклику `something` методу:

    >>> class ProductionClass:
    ...     def method(self):
    ...         self.something(1, 2, 3)
    ...     def something(self, a, b, c):
    ...         pass
    ...
    >>> real = ProductionClass()
    >>> real.something = MagicMock()
    >>> real.method()
    >>> real.something.assert_called_once_with(1, 2, 3)

### Макет для викликів методів об’єкта

В останньому прикладі ми патчили метод безпосередньо на об’єкті, щоб перевірити, чи правильно його викликали. Іншим поширеним випадком використання є передача об’єкта в метод (або деяку частину тестованої системи), а потім перевірка його правильного використання.

Простий `ProductionClass` нижче має метод `closer`. Якщо він викликається з об’єктом, він викликає для нього `close`.

    >>> class ProductionClass:
    ...     def closer(self, something):
    ...         something.close()
    ...

Отже, щоб перевірити його, нам потрібно передати об’єкт за допомогою методу `close` і перевірити, чи він правильно викликаний.

    >>> real = ProductionClass()
    >>> mock = Mock()
    >>> real.closer(mock)
    >>> mock.close.assert_called_with()

Нам не потрібно нічого робити, щоб забезпечити метод «close» у нашому макеті. Доступ до close створює його. Отже, якщо „close“ ще не було викликано, тоді доступ до нього в тесті створить його, але `assert_ called_with()` викличе виняток помилки.

### Знущальні класи

Поширений випадок використання - це макетування класів, створених вашим тестованим кодом. Коли ви виправляєте клас, цей клас замінюється макетом. Екземпляри створюються шляхом виклику класу. Це означає, що ви отримуєте доступ до «макетного екземпляра», дивлячись на значення, що повертається імітованим класом.

У наведеному нижче прикладі ми маємо функцію `some_function`, яка створює екземпляр `Foo` і викликає на ньому метод. Виклик `patch()` замінює клас `Foo` на макет. Екземпляр `Foo` є результатом виклику mock, тому він налаштовується шляхом модифікації mock `return_value`.

    >>> def some_function():
    ...     instance = module.Foo()
    ...     return instance.method()
    ...
    >>> with patch('module.Foo') as mock:
    ...     instance = mock.return_value
    ...     instance.method.return_value = 'the result'
    ...     result = some_function()
    ...     assert result == 'the result'

### Називання своїх макетів

Може бути корисно дати своїм макетам назву. Ім’я відображається у відображенні макета та може бути корисним, коли макет з’являється в повідомленнях про помилку тесту. Назва також поширюється на атрибути або методи макета:

    >>> mock = MagicMock(name='foo')
    >>> mock
    <MagicMock name='foo' id='...'>
    >>> mock.method
    <MagicMock name='foo.method' id='...'>

### Відстеження всіх дзвінків

Часто потрібно відстежувати більше ніж один виклик методу. Атрибут `mock_calls` записує всі виклики дочірніх атрибутів mock, а також до їхніх дочірніх атрибутів.

    >>> mock = MagicMock()
    >>> mock.method()
    <MagicMock name='mock.method()' id='...'>
    >>> mock.attribute.method(10, x=53)
    <MagicMock name='mock.attribute.method()' id='...'>
    >>> mock.mock_calls
    [call.method(), call.attribute.method(10, x=53)]

Якщо ви робите твердження щодо `mock_calls` і були викликані будь-які неочікувані методи, тоді твердження не вдасться. Це корисно, оскільки ви не тільки стверджуєте, що дзвінки, які ви очікували, були здійснені, але й перевіряєте, що вони були здійснені в правильному порядку та без додаткових дзвінків:

Ви використовуєте об’єкт `call` для створення списків для порівняння з `mock_calls`:

    >>> expected = [call.method(), call.attribute.method(10, x=53)]
    >>> mock.mock_calls == expected
    True

Однак параметри викликів, які повертають імітації, не записуються, що означає, що неможливо відстежувати вкладені виклики, де важливі параметри, які використовуються для створення предків:

    >>> m = Mock()
    >>> m.factory(important=True).deliver()
    <Mock name='mock.factory().deliver()' id='...'>
    >>> m.mock_calls[-1] == call.factory(important=False).deliver()
    True

### Налаштування повернених значень і атрибутів

Встановлення повернених значень для макетного об’єкта тривіально легко:

    >>> mock = Mock()
    >>> mock.return_value = 3
    >>> mock()
    3

Звичайно, ви можете зробити те саме для методів на mock:

    >>> mock = Mock()
    >>> mock.method.return_value = 3
    >>> mock.method()
    3

Повернене значення також можна встановити в конструкторі:

    >>> mock = Mock(return_value=3)
    >>> mock()
    3

Якщо вам потрібне налаштування атрибута на макеті, просто зробіть це:

    >>> mock = Mock()
    >>> mock.x = 3
    >>> mock.x
    3

Іноді потрібно змакувати складнішу ситуацію, як, наприклад, `mock.connection.cursor().execute("SELECT 1")`. Якщо ми хочемо, щоб цей виклик повертав список, ми повинні налаштувати результат вкладеного виклику.

Ми можемо використовувати `call`, щоб створити набір викликів у «ланцюжковому виклику», подібному до цього для легкого твердження згодом:

    >>> mock = Mock()
    >>> cursor = mock.connection.cursor.return_value
    >>> cursor.execute.return_value = ['foo']
    >>> mock.connection.cursor().execute("SELECT 1")
    ['foo']
    >>> expected = call.connection.cursor().execute("SELECT 1").call_list()
    >>> mock.mock_calls
    [call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
    >>> mock.mock_calls == expected
    True

Саме виклик `.call_list()` перетворює наш об’єкт виклику на список викликів, що представляють ланцюгові виклики.

### Створення винятків за допомогою моків

Корисним атрибутом є `side_effect`. Якщо ви встановите це як клас винятку або екземпляр, тоді виняток буде викликано під час виклику макета.

    >>> mock = Mock(side_effect=Exception('Boom!'))
    >>> mock()
    Traceback (most recent call last):
      ...
    Exception: Boom!

### Функції побічних ефектів та ітерації

`side_effect` також може бути встановлено як функція або iterable. Варіант використання `side_effect` як ітерації полягає в тому, що ваш макет буде викликатися кілька разів, і ви хочете, щоб кожен виклик повертав інше значення. Коли ви встановлюєте `side_effect` на iterable, кожен виклик mock повертає наступне значення з iterable:

    >>> mock = MagicMock(side_effect=[4, 5, 6])
    >>> mock()
    4
    >>> mock()
    5
    >>> mock()
    6

Для більш складних випадків використання, як-от динамічне змінення повернених значень залежно від того, з чим викликається макет, `side_effect` може бути функцією. Функція буде викликана з тими самими аргументами, що й макет. Усе, що повертає функція, повертає виклик:

    >>> vals = {(1, 2): 1, (2, 3): 2}
    >>> def side_effect(*args):
    ...     return vals[args]
    ...
    >>> mock = MagicMock(side_effect=side_effect)
    >>> mock(1, 2)
    1
    >>> mock(2, 3)
    2

### Знущання над асинхронними ітераторами

Починаючи з Python 3.8, `AsyncMock` і `MagicMock` підтримують імітацію Асинхронні ітератори через `__aiter__`. Атрибут `return_value` `__aiter__` можна використовувати для встановлення повернених значень, які будуть використовуватися для ітерації.

    >>> mock = MagicMock()  # AsyncMock also works here
    >>> mock.__aiter__.return_value = [1, 2, 3]
    >>> async def main():
    ...     return [i async for i in mock]
    ...
    >>> asyncio.run(main())
    [1, 2, 3]

### Знущання над асинхронним контекстним менеджером

Починаючи з Python 3.8, `AsyncMock` і `MagicMock` підтримують імітацію Менеджери асинхронного контексту через `__aenter__` і `__aexit__`. За замовчуванням `__aenter__` і `__aexit__` є екземплярами `AsyncMock`, які повертають асинхронну функцію.

    >>> class AsyncContextManager:
    ...     async def __aenter__(self):
    ...         return self
    ...     async def __aexit__(self, exc_type, exc, tb):
    ...         pass
    ...
    >>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here
    >>> async def main():
    ...     async with mock_instance as result:
    ...         pass
    ...
    >>> asyncio.run(main())
    >>> mock_instance.__aenter__.assert_awaited_once()
    >>> mock_instance.__aexit__.assert_awaited_once()

### Створення макету з існуючого об’єкта

Однією з проблем із надмірним використанням mocking є те, що воно поєднує ваші тести з реалізацією ваших mocks, а не з вашим реальним кодом. Припустімо, у вас є клас, який реалізує `some_method`. У тесті для іншого класу ви надаєте макет цього об’єкта, який також надає `some_method`. Якщо пізніше ви переробите перший клас, щоб він більше не мав `some_method`, тоді ваші тести продовжуватимуть проходити, навіть якщо ваш код зараз зламаний!

`Mock` дозволяє вам надати об’єкт як специфікацію для mock, використовуючи аргумент ключового слова spec. Доступ до методів/атрибутів у макеті, які не існують у вашому об’єкті специфікації, негайно призведе до помилки атрибута. Якщо ви зміните реалізацію вашої специфікації, тоді тести, які використовують цей клас, негайно почнуть виходити з ладу без необхідності створення екземпляра класу в цих тестах.

    >>> mock = Mock(spec=SomeClass)
    >>> mock.old_method()
    Traceback (most recent call last):
       ...
    AttributeError: object has no attribute 'old_method'

Використання специфікації також дає змогу розумніше зіставляти виклики, зроблені для mock, незалежно від того, чи були деякі параметри передані як позиційні чи іменовані аргументи:

    >>> def f(a, b, c): pass
    ...
    >>> mock = Mock(spec=f)
    >>> mock(1, 2, 3)
    <Mock name='mock()' id='140161580456576'>
    >>> mock.assert_called_with(a=1, b=2, c=3)

Якщо ви бажаєте, щоб ця розумніша відповідність також працювала з викликами методів на mock, ви можете використовувати auto-speccing.

Якщо вам потрібна сильніша форма специфікації, яка запобігає встановленню довільних атрибутів, а також їх отриманню, ви можете використовувати spec_set замість spec.

## Декоратори патчів

Примітка

З `patch()` важливо, щоб ви латали об’єкти в просторі імен, де вони шукаються. Зазвичай це просто, але для короткого посібника прочитайте де патч.

Загальною потребою в тестах є виправлення атрибута класу або атрибута модуля, наприклад, виправлення вбудованого модуля або виправлення класу в модулі, щоб перевірити, чи створено його екземпляр. Модулі та класи фактично є глобальними, тому після тесту потрібно скасувати їх виправлення, інакше виправлення збережеться в інших тестах і спричинить проблеми, які важко діагностувати.

mock надає для цього три зручні декоратори: `patch()`, `patch.object()` і `patch.dict()`. `patch` приймає один рядок у формі `package.module.Class.attribute`, щоб визначити атрибут, який ви виправляєте. Він також необов’язково приймає значення, яким ви бажаєте замінити атрибут (або клас чи щось інше). „patch.object“ приймає об’єкт і ім’я атрибута, який ви бажаєте виправити, а також необов’язково значення, з яким його потрібно виправити.

`patch.object`:

    >>> original = SomeClass.attribute
    >>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
    ... def test():
    ...     assert SomeClass.attribute == sentinel.attribute
    ...
    >>> test()
    >>> assert SomeClass.attribute == original

    >>> @patch('package.module.attribute', sentinel.attribute)
    ... def test():
    ...     from package.module import attribute
    ...     assert attribute is sentinel.attribute
    ...
    >>> test()

Якщо ви виправляєте модуль (включаючи `builtins`), використовуйте `patch()` замість `patch.object()`:

    >>> mock = MagicMock(return_value=sentinel.file_handle)
    >>> with patch('builtins.open', mock):
    ...     handle = open('filename', 'r')
    ...
    >>> mock.assert_called_with('filename', 'r')
    >>> assert handle == sentinel.file_handle, "incorrect file handle returned"

Ім’я модуля може бути розділене крапкою у формі `package.module`, якщо необхідно:

    >>> @patch('package.module.ClassName.attribute', sentinel.attribute)
    ... def test():
    ...     from package.module import ClassName
    ...     assert ClassName.attribute == sentinel.attribute
    ...
    >>> test()

Хороший зразок — це фактично прикрасити самі методи тестування:

    >>> class MyTest(unittest.TestCase):
    ...     @patch.object(SomeClass, 'attribute', sentinel.attribute)
    ...     def test_something(self):
    ...         self.assertEqual(SomeClass.attribute, sentinel.attribute)
    ...
    >>> original = SomeClass.attribute
    >>> MyTest('test_something').test_something()
    >>> assert SomeClass.attribute == original

Якщо ви хочете виправити за допомогою Mock, ви можете використовувати `patch()` лише з одним аргументом (або `patch.object()` з двома аргументами). Макет буде створено для вас і передано в тестову функцію/метод:

    >>> class MyTest(unittest.TestCase):
    ...     @patch.object(SomeClass, 'static_method')
    ...     def test_something(self, mock_method):
    ...         SomeClass.static_method()
    ...         mock_method.assert_called_with()
    ...
    >>> MyTest('test_something').test_something()

Ви можете скласти кілька декораторів патчів, використовуючи цей шаблон:

    >>> class MyTest(unittest.TestCase):
    ...     @patch('package.module.ClassName1')
    ...     @patch('package.module.ClassName2')
    ...     def test_something(self, MockClass2, MockClass1):
    ...         self.assertIs(package.module.ClassName1, MockClass1)
    ...         self.assertIs(package.module.ClassName2, MockClass2)
    ...
    >>> MyTest('test_something').test_something()

Коли ви вкладаєте декоратори латок, макети передаються до декорованої функції в тому ж порядку, в якому вони застосовані (звичайний Python порядок застосування декораторів). Це означає знизу вгору, тому у наведеному вище прикладі спочатку передається макет для `test_module.ClassName2`.

Існує також `patch.dict()` для встановлення значень у словнику лише під час області видимості та відновлення словника до вихідного стану після завершення тесту:

    >>> foo = {'key': 'value'}
    >>> original = foo.copy()
    >>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
    ...     assert foo == {'newkey': 'newvalue'}
    ...
    >>> assert foo == original

`patch`, `patch.object` і `patch.dict` можна використовувати як контекстні менеджери.

Там, де ви використовуєте `patch()` для створення макету, ви можете отримати посилання на макет за допомогою форми «as» оператора with:

    >>> class ProductionClass:
    ...     def method(self):
    ...         pass
    ...
    >>> with patch.object(ProductionClass, 'method') as mock_method:
    ...     mock_method.return_value = None
    ...     real = ProductionClass()
    ...     real.method(1, 2, 3)
    ...
    >>> mock_method.assert_called_with(1, 2, 3)

Як альтернативу `patch`, `patch.object` і `patch.dict` можна використовувати як декоратори класів. При такому використанні це те саме, що застосувати декоратор окремо до кожного методу, назва якого починається з «test».

## Подальші приклади

Ось ще кілька прикладів для деяких трохи складніших сценаріїв.

### Знущальні ланцюгові дзвінки

Висмішувати ланцюгові виклики насправді просто за допомогою mock, коли ви розумієте атрибут `return_value`. Коли макет викликається вперше або ви отримуєте його `return_value` до його виклику, створюється новий `Mock`.

Це означає, що ви можете побачити, як об’єкт, повернутий викликом імітованого об’єкта, використовувався, запитуючи макет `return_value`:

    >>> mock = Mock()
    >>> mock().foo(a=2, b=3)
    <Mock name='mock().foo()' id='...'>
    >>> mock.return_value.foo.assert_called_with(a=2, b=3)

Звідси це простий крок, щоб налаштувати та зробити твердження щодо ланцюжкових викликів. Звісно, іншою альтернативою є написання коду більш придатним для перевірки способом…

Отже, припустімо, що ми маємо код, який виглядає приблизно так:

    >>> class Something:
    ...     def __init__(self):
    ...         self.backend = BackendProvider()
    ...     def method(self):
    ...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
    ...         # more code

Якщо припустити, що `BackendProvider` вже добре перевірено, як ми перевіримо `method()`? Зокрема, ми хочемо перевірити, чи розділ коду `# more code` правильно використовує об’єкт відповіді.

Оскільки цей ланцюжок викликів здійснюється з атрибута екземпляра, ми можемо виправити атрибут `backend` на екземплярі `Something`. У цьому конкретному випадку нас цікавить лише значення, яке повертає останній виклик `start_call`, тому нам не потрібно робити багато налаштувань. Припустімо, що об’єкт, який він повертає, є «файлоподібним», тому ми переконаємося, що наш об’єкт відповіді використовує вбудований `open()` як свою `специфікацію`.

Для цього ми створюємо макет екземпляра як наш макет бекенда та створюємо для нього об’єкт імітації відповіді. Щоб встановити відповідь як значення, що повертається для цього останнього `start_call`, ми можемо зробити так:

    mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response

Ми можемо зробити це трохи зручнішим способом, використовуючи метод `configure_mock()`, щоб напряму встановити значення, що повертається:

    >>> something = Something()
    >>> mock_response = Mock(spec=open)
    >>> mock_backend = Mock()
    >>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
    >>> mock_backend.configure_mock(**config)

За допомогою них ми, як мавпи, виправляємо «імітацію бекенда» на місці та можемо зробити справжній виклик:

    >>> something.backend = mock_backend
    >>> something.method()

Використовуючи `mock_calls`, ми можемо перевірити ланцюжковий виклик за допомогою одного твердження. Зв’язаний виклик — це кілька викликів в одному рядку коду, тому в `mock_calls` буде кілька записів. Ми можемо використовувати `call.call_list()`, щоб створити цей список дзвінків для нас:

    >>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
    >>> call_list = chained.call_list()
    >>> assert mock_backend.mock_calls == call_list

### Часткове глузування

In some tests I wanted to mock out a call to `datetime.date.today()` to return a known date, but I didn’t want to prevent the code under test from creating new date objects. Unfortunately `datetime.date` is written in C, and so I couldn’t just monkey-patch out the static `date.today()` method.

Я знайшов простий спосіб зробити це, який передбачав ефективне обгортання класу дати макетом, але передачу викликів конструктору до реального класу (і повернення реальних екземплярів).

The `patch decorator` is used here to mock out the `date` class in the module under test. The `side_effect` attribute on the mock date class is then set to a lambda function that returns a real date. When the mock date class is called a real date will be constructed and returned by `side_effect`.

    >>> from datetime import date
    >>> with patch('mymodule.date') as mock_date:
    ...     mock_date.today.return_value = date(2010, 10, 8)
    ...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
    ...
    ...     assert mymodule.date.today() == date(2010, 10, 8)
    ...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)

Зверніть увагу, що ми не виправляємо `datetime.date` глобально, ми виправляємо `date` в модулі, який використовує його. Дивіться де виправити.

Коли викликається `date.today()`, повертається відома дата, але виклики конструктора `date(...)` повертають звичайні дати. Без цього вам може знадобитися розрахувати очікуваний результат, використовуючи точно такий самий алгоритм, як і тестовий код, який є класичним тестовим антишаблоном.

Виклики конструктора дат записуються в атрибутах mock_date (call_count і friends), що також може бути корисним для ваших тестів.

Альтернативний спосіб роботи з імітаційними датами або іншими вбудованими класами обговорюється в цьому записі блогу.

### Висміювання методу генератора

Генератор Python — це функція або метод, який використовує оператор `yield` для повернення ряду значень під час ітерації 1.

Метод/функція генератора викликається для повернення об’єкта генератора. Це об’єкт генератора, який потім повторюється. Методом протоколу для ітерації є `__iter__()`, тому ми можемо імітувати це за допомогою `MagicMock`.

Ось приклад класу з методом «iter», реалізованим як генератор:

    >>> class Foo:
    ...     def iter(self):
    ...         for i in [1, 2, 3]:
    ...             yield i
    ...
    >>> foo = Foo()
    >>> list(foo.iter())
    [1, 2, 3]

Як би ми висміювали цей клас, і зокрема його метод «iter»?

Щоб налаштувати значення, що повертаються з ітерації (неявно у виклику `list`), нам потрібно налаштувати об’єкт, що повертається викликом `foo.iter()`.

    >>> mock_foo = MagicMock()
    >>> mock_foo.iter.return_value = iter([1, 2, 3])
    >>> list(mock_foo.iter())
    [1, 2, 3]

1

  Існують також вирази-генератори та більш просунуті способи використання генераторів, але вони тут нас не хвилюють. Дуже гарний вступ до генераторів і їхньої потужності: Трюки генераторів для системних програмістів.

### Застосування того самого патча до кожного методу тестування

If you want several patches in place for multiple test methods the obvious way is to apply the patch decorators to every method. This can feel like unnecessary repetition. For Python 2.6 or more recent you can use `patch()` (in all its various forms) as a class decorator. This applies the patches to all test methods on the class. A test method is identified by methods whose names start with `test`:

    >>> @patch('mymodule.SomeClass')
    ... class MyTest(unittest.TestCase):
    ...
    ...     def test_one(self, MockSomeClass):
    ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
    ...
    ...     def test_two(self, MockSomeClass):
    ...         self.assertIs(mymodule.SomeClass, MockSomeClass)
    ...
    ...     def not_a_test(self):
    ...         return 'something'
    ...
    >>> MyTest('test_one').test_one()
    >>> MyTest('test_two').test_two()
    >>> MyTest('test_two').not_a_test()
    'something'

Альтернативним способом керування патчами є використання методи латання: запуск і зупинка. Це дозволяє вам перемістити виправлення у ваші методи `setUp` і `tearDown`.

    >>> class MyTest(unittest.TestCase):
    ...     def setUp(self):
    ...         self.patcher = patch('mymodule.foo')
    ...         self.mock_foo = self.patcher.start()
    ...
    ...     def test_foo(self):
    ...         self.assertIs(mymodule.foo, self.mock_foo)
    ...
    ...     def tearDown(self):
    ...         self.patcher.stop()
    ...
    >>> MyTest('test_foo').run()

Якщо ви використовуєте цю техніку, ви повинні переконатися, що виправлення «скасовано», викликавши `stop`. Це може бути складніше, ніж ви могли б подумати, тому що якщо виняток виникає в SetUp, tearDown не викликається. `unittest.TestCase.addCleanup()` полегшує це:

    >>> class MyTest(unittest.TestCase):
    ...     def setUp(self):
    ...         patcher = patch('mymodule.foo')
    ...         self.addCleanup(patcher.stop)
    ...         self.mock_foo = patcher.start()
    ...
    ...     def test_foo(self):
    ...         self.assertIs(mymodule.foo, self.mock_foo)
    ...
    >>> MyTest('test_foo').run()

### Висміювання незв’язаних методів

Сьогодні під час написання тестів мені потрібно було виправити незв’язаний метод (виправити метод у класі, а не в екземплярі). Мені потрібно було передати self як перший аргумент, оскільки я хочу зробити твердження про те, які об’єкти викликають цей конкретний метод. Проблема полягає в тому, що ви не можете виправити за допомогою макету для цього, тому що якщо ви заміните неприв’язаний метод на макет, він не стане зв’язаним методом під час отримання з примірника, і тому він не передається самостійно. Обхідним шляхом є заміна незв’язаного методу реальною функцією. Декоратор `patch()` робить так простим виправлення методів за допомогою макету, що створення справжньої функції стає незручністю.

Якщо ви передаєте `autospec=True` для виправлення, тоді він виконує виправлення за допомогою реального функціонального об’єкта. Цей функціональний об’єкт має таку саму сигнатуру, як і той, який він замінює, але делегує макет під капотом. Ви все ще автоматично створюєте свій макет точно так само, як і раніше. Однак це означає, що якщо ви використовуєте його для виправлення незв’язаного методу в класі, імітована функція буде перетворена на зв’язаний метод, якщо її буде отримано з екземпляра. Першим аргументом буде передано `self`, а це саме те, що я хотів:

    >>> class Foo:
    ...   def foo(self):
    ...     pass
    ...
    >>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
    ...   mock_foo.return_value = 'foo'
    ...   foo = Foo()
    ...   foo.foo()
    ...
    'foo'
    >>> mock_foo.assert_called_once_with(foo)

Якщо ми не використовуємо `autospec=True`, тоді неприв’язаний метод замість цього виправляється за допомогою екземпляра Mock і не викликається за допомогою `self`.

### Перевірка кількох викликів за допомогою імітації

mock має гарний API для створення тверджень про те, як використовуються ваші макетні об’єкти.

    >>> mock = Mock()
    >>> mock.foo_bar.return_value = None
    >>> mock.foo_bar('baz', spam='eggs')
    >>> mock.foo_bar.assert_called_with('baz', spam='eggs')

If your mock is only being called once you can use the `assert_called_once_with()` method that also asserts that the `call_count` is one.

    >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
    >>> mock.foo_bar()
    >>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
    Traceback (most recent call last):
        ...
    AssertionError: Expected to be called once. Called 2 times.

І `assert_ called_with`, і `assert_ called_once_with` створюють твердження щодо останнього виклику. Якщо ваш макет буде викликано кілька разів, і ви хочете зробити твердження щодо всіх цих викликів, ви можете використовувати `call_args_list`:

    >>> mock = Mock(return_value=None)
    >>> mock(1, 2, 3)
    >>> mock(4, 5, 6)
    >>> mock()
    >>> mock.call_args_list
    [call(1, 2, 3), call(4, 5, 6), call()]

Помічник `call` полегшує створення тверджень щодо цих викликів. Ви можете створити список очікуваних викликів і порівняти його з `call_args_list`. Це виглядає надзвичайно схожим на відображення `списку викликів_аргів`:

    >>> expected = [call(1, 2, 3), call(4, 5, 6), call()]
    >>> mock.call_args_list == expected
    True

### Робота зі змінними аргументами

Інша ситуація рідкісна, але може вас вкусити, коли вашу імітацію викликають зі змінними аргументами. `call_args` і `call_args_list` зберігають посилання на аргументи. Якщо аргументи змінено кодом, що тестується, ви більше не зможете робити твердження про те, якими були значення під час виклику макету.

Ось приклад коду, який показує проблему. Уявіть собі такі функції, визначені в „mymodule“:

    def frob(val):
        pass

    def grob(val):
        "First frob and then clear val"
        frob(val)
        val.clear()

Коли ми намагаємося перевірити, що `grob` викликає `frob` з правильним аргументом, подивіться, що відбувається:

    >>> with patch('mymodule.frob') as mock_frob:
    ...     val = {6}
    ...     mymodule.grob(val)
    ...
    >>> val
    set()
    >>> mock_frob.assert_called_with({6})
    Traceback (most recent call last):
        ...
    AssertionError: Expected: (({6},), {})
    Called with: ((set(),), {})

Однією з можливостей було б імітаційне копіювання аргументів, які ви передаєте. Це може спричинити проблеми, якщо ви робите твердження, які покладаються на ідентичність об’єкта для рівності.

Here’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.

    >>> from copy import deepcopy
    >>> from unittest.mock import Mock, patch, DEFAULT
    >>> def copy_call_args(mock):
    ...     new_mock = Mock()
    ...     def side_effect(*args, **kwargs):
    ...         args = deepcopy(args)
    ...         kwargs = deepcopy(kwargs)
    ...         new_mock(*args, **kwargs)
    ...         return DEFAULT
    ...     mock.side_effect = side_effect
    ...     return new_mock
    ...
    >>> with patch('mymodule.frob') as mock_frob:
    ...     new_mock = copy_call_args(mock_frob)
    ...     val = {6}
    ...     mymodule.grob(val)
    ...
    >>> new_mock.assert_called_with({6})
    >>> new_mock.call_args
    call({6})

`copy_call_args` викликається з макетом, який буде викликано. Він повертає новий макет, на основі якого ми виконуємо твердження. Функція `side_effect` створює копію аргументів і викликає наш `new_mock` разом із копією.

Примітка

Якщо ваш макет використовуватиметься лише один раз, є простіший спосіб перевірити аргументи в момент їх виклику. Ви можете просто зробити перевірку всередині функції `side_effect`.

    >>> def side_effect(arg):
    ...     assert arg == {6}
    ...
    >>> mock = Mock(side_effect=side_effect)
    >>> mock({6})
    >>> mock(set())
    Traceback (most recent call last):
        ...
    AssertionError

Альтернативним підходом є створення підкласу `Mock` або `MagicMock`, який копіює (за допомогою `copy.deepcopy()`) аргументи. Ось приклад реалізації:

    >>> from copy import deepcopy
    >>> class CopyingMock(MagicMock):
    ...     def __call__(self, /, *args, **kwargs):
    ...         args = deepcopy(args)
    ...         kwargs = deepcopy(kwargs)
    ...         return super().__call__(*args, **kwargs)
    ...
    >>> c = CopyingMock(return_value=None)
    >>> arg = set()
    >>> c(arg)
    >>> arg.add(1)
    >>> c.assert_called_with(set())
    >>> c.assert_called_with(arg)
    Traceback (most recent call last):
        ...
    AssertionError: Expected call: mock({1})
    Actual call: mock(set())
    >>> c.foo
    <CopyingMock name='mock.foo' id='...'>

Коли ви створюєте підклас `Mock` або `MagicMock`, усі динамічно створені атрибути та `return_value` автоматично використовуватимуть ваш підклас. Це означає, що всі нащадки `CopyingMock` також матимуть тип `CopyingMock`.

### Патчі гніздування

Використовувати patch як менеджер контексту добре, але якщо ви робите кілька патчів, ви можете отримати вкладені з операторами відступи все далі й далі праворуч:

    >>> class MyTest(unittest.TestCase):
    ...
    ...     def test_foo(self):
    ...         with patch('mymodule.Foo') as mock_foo:
    ...             with patch('mymodule.Bar') as mock_bar:
    ...                 with patch('mymodule.Spam') as mock_spam:
    ...                     assert mymodule.Foo is mock_foo
    ...                     assert mymodule.Bar is mock_bar
    ...                     assert mymodule.Spam is mock_spam
    ...
    >>> original = mymodule.Foo
    >>> MyTest('test_foo').test_foo()
    >>> assert mymodule.Foo is original

За допомогою функцій очищення unittest і методи латання: запуск і зупинка ми можемо досягти того самого ефекту без вкладеного відступу. Простий допоміжний метод, `create_patch`, ставить патч на місце та повертає створений макет для нас:

    >>> class MyTest(unittest.TestCase):
    ...
    ...     def create_patch(self, name):
    ...         patcher = patch(name)
    ...         thing = patcher.start()
    ...         self.addCleanup(patcher.stop)
    ...         return thing
    ...
    ...     def test_foo(self):
    ...         mock_foo = self.create_patch('mymodule.Foo')
    ...         mock_bar = self.create_patch('mymodule.Bar')
    ...         mock_spam = self.create_patch('mymodule.Spam')
    ...
    ...         assert mymodule.Foo is mock_foo
    ...         assert mymodule.Bar is mock_bar
    ...         assert mymodule.Spam is mock_spam
    ...
    >>> original = mymodule.Foo
    >>> MyTest('test_foo').run()
    >>> assert mymodule.Foo is original

### Знущання над словником за допомогою MagicMock

Можливо, ви захочете імітувати словник або інший об’єкт-контейнер, записуючи всі доступи до нього, водночас залишаючи його поведінку як словник.

Ми можемо зробити це за допомогою `MagicMock`, який поводитиметься як словник, і за допомогою `side_effect` для делегування доступу до словника справжньому базовому словнику, який знаходиться під нашим контролем.

When the `__getitem__()` and `__setitem__()` methods of our `MagicMock` are called (normal dictionary access) then `side_effect` is called with the key (and in the case of `__setitem__` the value too). We can also control what is returned.

Після використання `MagicMock` ми можемо використовувати такі атрибути, як `call_args_list`, щоб підтвердити, як використовувався словник:

    >>> my_dict = {'a': 1, 'b': 2, 'c': 3}
    >>> def getitem(name):
    ...      return my_dict[name]
    ...
    >>> def setitem(name, val):
    ...     my_dict[name] = val
    ...
    >>> mock = MagicMock()
    >>> mock.__getitem__.side_effect = getitem
    >>> mock.__setitem__.side_effect = setitem

Примітка

Альтернативою використанню `MagicMock` є використання `Mock` і лише надання магічних методів, які вам потрібні:

    >>> mock = Mock()
    >>> mock.__getitem__ = Mock(side_effect=getitem)
    >>> mock.__setitem__ = Mock(side_effect=setitem)

Третій варіант — використовувати `MagicMock`, але передаючи `dict` як аргумент spec (або spec_set), щоб створений `MagicMock` мав доступні лише магічні методи словника:

    >>> mock = MagicMock(spec_set=dict)
    >>> mock.__getitem__.side_effect = getitem
    >>> mock.__setitem__.side_effect = setitem

З цими функціями побічних ефектів `мокет` поводитиметься як звичайний словник, але записуватиме доступ. Він навіть викликає `KeyError`, якщо ви намагаєтеся отримати доступ до ключа, якого не існує.

    >>> mock['a']
    1
    >>> mock['c']
    3
    >>> mock['d']
    Traceback (most recent call last):
        ...
    KeyError: 'd'
    >>> mock['b'] = 'fish'
    >>> mock['d'] = 'eggs'
    >>> mock['b']
    'fish'
    >>> mock['d']
    'eggs'

Після його використання ви можете робити твердження щодо доступу за допомогою звичайних методів і атрибутів:

    >>> mock.__getitem__.call_args_list
    [call('a'), call('c'), call('d'), call('b'), call('d')]
    >>> mock.__setitem__.call_args_list
    [call('b', 'fish'), call('d', 'eggs')]
    >>> my_dict
    {'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}

### Макетні підкласи та їхні атрибути

Існують різні причини, чому ви можете створити підклас `Mock`. Однією з причин може бути додавання допоміжних методів. Ось дурний приклад:

    >>> class MyMock(MagicMock):
    ...     def has_been_called(self):
    ...         return self.called
    ...
    >>> mymock = MyMock(return_value=None)
    >>> mymock
    <MyMock id='...'>
    >>> mymock.has_been_called()
    False
    >>> mymock()
    >>> mymock.has_been_called()
    True

Стандартна поведінка екземплярів `Mock` полягає в тому, що атрибути та значення, що повертаються mocks, мають той самий тип, що й макет, з якого до них здійснюється доступ. Це гарантує, що атрибути `Mock` є `Mocks`, а `MagicMock` атрибути `MagicMocks` 2. Отже, якщо ви створюєте підкласи для додавання допоміжних методів, вони також будуть доступні в атрибутах і повертаються значеннях екземплярів вашого підкласу.

    >>> mymock.foo
    <MyMock name='mock.foo' id='...'>
    >>> mymock.foo.has_been_called()
    False
    >>> mymock.foo()
    <MyMock name='mock.foo()' id='...'>
    >>> mymock.foo.has_been_called()
    True

Sometimes this is inconvenient. For example, one user is subclassing mock to created a Twisted adaptor. Having this applied to attributes too actually causes errors.

`Mock` (у всіх його варіантах) використовує метод під назвою `_get_child_mock`, щоб створити ці «під-моки» для атрибутів і повертаються значень. Ви можете запобігти використанню вашого підкласу для атрибутів, замінивши цей метод. Підпис полягає в тому, що він приймає довільні аргументи ключового слова (`**kwargs`), які потім передаються в макетний конструктор:

    >>> class Subclass(MagicMock):
    ...     def _get_child_mock(self, /, **kwargs):
    ...         return MagicMock(**kwargs)
    ...
    >>> mymock = Subclass()
    >>> mymock.foo
    <MagicMock name='mock.foo' id='...'>
    >>> assert isinstance(mymock, Subclass)
    >>> assert not isinstance(mymock.foo, Subclass)
    >>> assert not isinstance(mymock(), Subclass)

2

  Винятком із цього правила є невикликані моки. Атрибути використовують викликаючий варіант, оскільки інакше невикликані макети не могли б мати викликані методи.

### Знущання над імпортом за допомогою patch.dict

Одна ситуація, коли насмішка може бути важкою, це коли у вас є локальний імпорт всередині функції. Їх важче висміяти, оскільки вони не використовують об’єкт із простору імен модуля, який ми можемо виправити.

Загалом слід уникати місцевого імпорту. Іноді їх роблять, щоб запобігти циклічним залежностям, для яких зазвичай існує набагато кращий спосіб вирішити проблему (рефакторити код) або запобігти «попереднім витратам» шляхом затримки імпорту. Це також можна вирішити кращим способом, ніж безумовний локальний імпорт (зберігайте модуль як клас або атрибут модуля та виконуйте імпорт лише під час першого використання).

That aside there is a way to use `mock` to affect the results of an import. Importing fetches an object from the `sys.modules` dictionary. Note that it fetches an object, which need not be a module. Importing a module for the first time results in a module object being put in `sys.modules`, so usually when you import something you get a module back. This need not be the case however.

Це означає, що ви можете використовувати `patch.dict()`, щоб тимчасово розмістити макет на місці в `sys.modules`. Будь-який імпорт, поки цей патч активний, отримуватиме макет. Коли виправлення завершено (декорована функція завершує роботу, тіло оператора with завершено або викликається `patcher.stop()`), тоді все, що було раніше, буде безпечно відновлено.

Ось приклад, який висміює модуль «fooble».

    >>> import sys
    >>> mock = Mock()
    >>> with patch.dict('sys.modules', {'fooble': mock}):
    ...    import fooble
    ...    fooble.blob()
    ...
    <Mock name='mock.blob()' id='...'>
    >>> assert 'fooble' not in sys.modules
    >>> mock.blob.assert_called_once_with()

Як ви бачите, `імпорт fooble` вдається, але після виходу в `sys.modules` не залишилося жодного „fooble“.

Це також працює для форми `from module import name`:

    >>> mock = Mock()
    >>> with patch.dict('sys.modules', {'fooble': mock}):
    ...    from fooble import blob
    ...    blob.blip()
    ...
    <Mock name='mock.blob.blip()' id='...'>
    >>> mock.blob.blip.assert_called_once_with()

Доклавши трохи більше зусиль, ви також можете імітувати імпорт пакетів:

    >>> mock = Mock()
    >>> modules = {'package': mock, 'package.module': mock.module}
    >>> with patch.dict('sys.modules', modules):
    ...    from package.module import fooble
    ...    fooble()
    ...
    <Mock name='mock.module.fooble()' id='...'>
    >>> mock.module.fooble.assert_called_once_with()

### Відстеження порядку викликів і менш докладні підтвердження викликів

Клас `Mock` дозволяє вам відстежувати порядок викликів методів ваших фіктивних об’єктів за допомогою атрибута `method_calls`. Це не дозволяє відстежувати порядок викликів між окремими макетними об’єктами, однак ми можемо використовувати `mock_calls` для досягнення того самого ефекту.

Оскільки mocks відстежують виклики дочірніх mocks у `mock_calls`, а доступ до довільного атрибута mock створює дочірній mock, ми можемо створювати окремі моки від батьківського. Виклики цих дочірніх маніпуляцій будуть записані по порядку в `mock_calls` батьківського:

    >>> manager = Mock()
    >>> mock_foo = manager.foo
    >>> mock_bar = manager.bar

    >>> mock_foo.something()
    <Mock name='mock.foo.something()' id='...'>
    >>> mock_bar.other.thing()
    <Mock name='mock.bar.other.thing()' id='...'>

    >>> manager.mock_calls
    [call.foo.something(), call.bar.other.thing()]

Потім ми можемо стверджувати про виклики, включаючи порядок, порівнюючи з атрибутом `mock_calls` макет менеджера:

    >>> expected_calls = [call.foo.something(), call.bar.other.thing()]
    >>> manager.mock_calls == expected_calls
    True

Якщо `patch` створює та розміщує ваші макети, ви можете прикріпити їх до макета менеджера за допомогою методу `attach_mock()`. Після прикріплення виклики будуть записані в `mock_calls` менеджера.

    >>> manager = MagicMock()
    >>> with patch('mymodule.Class1') as MockClass1:
    ...     with patch('mymodule.Class2') as MockClass2:
    ...         manager.attach_mock(MockClass1, 'MockClass1')
    ...         manager.attach_mock(MockClass2, 'MockClass2')
    ...         MockClass1().foo()
    ...         MockClass2().bar()
    <MagicMock name='mock.MockClass1().foo()' id='...'>
    <MagicMock name='mock.MockClass2().bar()' id='...'>
    >>> manager.mock_calls
    [call.MockClass1(),
    call.MockClass1().foo(),
    call.MockClass2(),
    call.MockClass2().bar()]

Якщо було зроблено багато викликів, але вас цікавить лише певна їх послідовність, альтернативою є використання методу `assert_has_calls()`. Це бере список викликів (створений за допомогою об’єкта `call`). Якщо ця послідовність викликів міститься в `mock_calls`, тоді твердження буде успішним.

    >>> m = MagicMock()
    >>> m().foo().bar().baz()
    <MagicMock name='mock().foo().bar().baz()' id='...'>
    >>> m.one().two().three()
    <MagicMock name='mock.one().two().three()' id='...'>
    >>> calls = call.one().two().three().call_list()
    >>> m.assert_has_calls(calls)

Незважаючи на те, що зв’язаний виклик `m.one().two().three()` не єдиний виклик, який було зроблено для mock, твердження все одно успішне.

Іноді макет може мати кілька звернень до нього, і ви зацікавлені лише в підтвердженні деяких із цих викликів. Ви можете навіть не дбати про порядок. У цьому випадку ви можете передати `any_order=True` до `assert_has_calls`:

    >>> m = MagicMock()
    >>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')
    (...)
    >>> calls = [call.fifty('50'), call(1), call.seven(7)]
    >>> m.assert_has_calls(calls, any_order=True)

### Складніше зіставлення аргументів

Використовуючи ту саму основну концепцію, що й `ANY`, ми можемо реалізувати збіги для більш складних тверджень щодо об’єктів, які використовуються як аргументи для імітації.

Припустімо, ми очікуємо, що якийсь об’єкт буде передано макету, який за замовчуванням порівнює рівність на основі ідентичності об’єкта (що є стандартним значенням Python для визначених користувачем класів). Щоб використовувати `assert_ called_with()`, нам потрібно буде передати той самий об’єкт. Якщо нас цікавлять лише деякі атрибути цього об’єкта, ми можемо створити відповідник, який перевірить ці атрибути для нас.

У цьому прикладі ви можете побачити, як «стандартного» виклику `assert_ called_with` недостатньо:

    >>> class Foo:
    ...     def __init__(self, a, b):
    ...         self.a, self.b = a, b
    ...
    >>> mock = Mock(return_value=None)
    >>> mock(Foo(1, 2))
    >>> mock.assert_called_with(Foo(1, 2))
    Traceback (most recent call last):
        ...
    AssertionError: Expected: call(<__main__.Foo object at 0x...>)
    Actual call: call(<__main__.Foo object at 0x...>)

Функція порівняння для нашого класу `Foo` може виглядати приблизно так:

    >>> def compare(self, other):
    ...     if not type(self) == type(other):
    ...         return False
    ...     if self.a != other.a:
    ...         return False
    ...     if self.b != other.b:
    ...         return False
    ...     return True
    ...

І об’єкт відповідності, який може використовувати такі функції порівняння для своєї операції рівності, виглядатиме приблизно так:

    >>> class Matcher:
    ...     def __init__(self, compare, some_obj):
    ...         self.compare = compare
    ...         self.some_obj = some_obj
    ...     def __eq__(self, other):
    ...         return self.compare(self.some_obj, other)
    ...

Зібравши все це разом:

    >>> match_foo = Matcher(compare, Foo(1, 2))
    >>> mock.assert_called_with(match_foo)

Екземпляр `Matcher` створюється нашою функцією порівняння та об’єктом `Foo`, з яким ми хочемо порівняти. У `assert_ called_with` буде викликаний метод рівності `Matcher`, який порівнює об’єкт, з яким було викликано макет, з тим, з яким ми створили наш збігувач. Якщо вони збігаються, `assert_ called_with` пропускається, а якщо вони не збігаються, виникає `AssertionError`:

    >>> match_wrong = Matcher(compare, Foo(3, 4))
    >>> mock.assert_called_with(match_wrong)
    Traceback (most recent call last):
        ...
    AssertionError: Expected: ((<Matcher object at 0x...>,), {})
    Called with: ((<Foo object at 0x...>,), {})

Трохи налаштувавши функцію порівняння, ви могли б безпосередньо викликати `AssertionError` і надавати більш корисне повідомлення про помилку.

Починаючи з версії 1.5, тестова бібліотека Python PyHamcrest надає подібну функціональність, яка може бути корисною тут, у формі її відповідника рівності (hamcrest.library.integration.match_equality).