unittest.mock — библиотека насмешливых объектов

Добавлено в версии 3.3.

Исходный код: Lib/unittest/mock.py.


unittest.mock - это библиотека для тестирования в Python. Она позволяет заменять части тестируемой системы имитационными объектами и делать утверждения о том, как они были использованы.

unittest.mock предоставляет основной класс Mock, избавляющий от необходимости создавать множество заглушек в вашем тестовом наборе. После выполнения действия вы можете сделать утверждения о том, какие методы / атрибуты были использованы и с какими аргументами они были вызваны. Вы также можете указать возвращаемые значения и установить необходимые атрибуты обычным способом.

Кроме того, mock предоставляет декоратор patch(), который обрабатывает изменение атрибутов уровня модуля и класса в пределах области действия теста, а также sentinel для создания уникальных объектов. Примеры использования quick guide, Mock и MagicMock смотрите в patch().

Mock разработан для использования с unittest и основан на модели «действие -> утверждение» вместо «запись -> воспроизведение», используемой многими мокинг-фреймворками.

Существует бэкпорт unittest.mock для более ранних версий Python, доступный как mock on PyPI.

Краткое руководство

Объекты Mock и MagicMock создают все атрибуты и методы по мере обращения к ним и хранят сведения о том, как они были использованы. Вы можете настроить их, чтобы указать возвращаемые значения или ограничить доступность атрибутов, а затем сделать утверждения о том, как они были использованы:

>>> from unittest.mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')

side_effect позволяет выполнять побочные эффекты, в том числе вызывать исключение при вызове имитатора:

>>> mock = Mock(side_effect=KeyError('foo'))
>>> mock()
Traceback (most recent call last):
 ...
KeyError: 'foo'
>>> values = {'a': 1, 'b': 2, 'c': 3}
>>> def side_effect(arg):
...     return values[arg]
...
>>> mock.side_effect = side_effect
>>> mock('a'), mock('b'), mock('c')
(1, 2, 3)
>>> mock.side_effect = [5, 4, 3, 2, 1]
>>> mock(), mock(), mock()
(5, 4, 3)

Mock имеет множество других способов, с помощью которых вы можете настроить его и управлять его поведением. Например, аргумент spec настраивает имитатор на получение спецификации от другого объекта. Попытка получить доступ к атрибутам или методам макета, которые не существуют в спецификации, завершится неудачей с ошибкой AttributeError.

Декоратор patch() / менеджер контекста позволяет легко подражать классам или объектам в тестируемом модуле. Указанный вами объект будет заменен имитатором (или другим объектом) во время тестирования и восстановлен по его окончании:

>>> from unittest.mock import patch
>>> @patch('module.ClassName2')
... @patch('module.ClassName1')
... def test(MockClass1, MockClass2):
...     module.ClassName1()
...     module.ClassName2()
...     assert MockClass1 is module.ClassName1
...     assert MockClass2 is module.ClassName2
...     assert MockClass1.called
...     assert MockClass2.called
...
>>> test()

Примечание

При вложении декораторов патчей имитаторы передаются в декорированную функцию в том же порядке, в котором они применялись (обычный Python порядок применения декораторов). Это значит снизу вверх, поэтому в приведенном выше примере сначала передается имитатор для module.ClassName1.

При использовании patch() важно, чтобы вы сопрягали объекты в том пространстве имен, в котором они ищутся. Обычно это просто, но для краткого руководства прочтите where to patch.

Как и декоратор patch() может использоваться в качестве менеджера контекста в операторе with:

>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)

Существует также patch.dict() для установки значений в словарь непосредственно во время выполнения и восстановления словаря в исходное состояние по окончании теста:

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

Mock поддерживает высмеивание Python magic methods. Самый простой способ использования магических методов - это класс MagicMock. Он позволяет вам делать такие вещи, как:

>>> mock = MagicMock()
>>> mock.__str__.return_value = 'foobarbaz'
>>> str(mock)
'foobarbaz'
>>> mock.__str__.assert_called_with()

Mock позволяет вам назначать функции (или другие экземпляры Mock) магическим методам, и они будут вызываться соответствующим образом. Класс MagicMock - это просто вариант Mock, в котором все магические методы предварительно созданы для вас (во всяком случае, все полезные).

Ниже приведен пример использования магических методов с обычным классом Mock:

>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='wheeeeee')
>>> str(mock)
'wheeeeee'

Чтобы убедиться, что объекты-макеты в ваших тестах имеют тот же api, что и объекты, которые они заменяют, вы можете использовать auto-speccing. Автоспецификация может быть выполнена с помощью аргумента autospec в патче или функции create_autospec(). Автоспецификация создает объекты-макеты, которые имеют те же атрибуты и методы, что и заменяемые ими объекты, а все функции и методы (включая конструкторы) имеют ту же сигнатуру вызова, что и реальный объект.

Это гарантирует, что при неправильном использовании ваши имитаторы будут работать так же, как и ваш производственный код:

>>> from unittest.mock import create_autospec
>>> def function(a, b, c):
...     pass
...
>>> mock_function = create_autospec(function, return_value='fishy')
>>> mock_function(1, 2, 3)
'fishy'
>>> mock_function.assert_called_once_with(1, 2, 3)
>>> mock_function('wrong arguments')
Traceback (most recent call last):
 ...
TypeError: <lambda>() takes exactly 3 arguments (1 given)

create_autospec() также можно использовать на классах, где он копирует сигнатуру метода __init__, и на вызываемых объектах, где он копирует сигнатуру метода __call__.

Шуточный класс

Mock - это гибкий объект mock, предназначенный для замены использования заглушек и тестовых дублей во всем вашем коде. Моки являются вызываемыми и создают атрибуты как новые моки, когда вы обращаетесь к ним 1. Обращение к одному и тому же атрибуту всегда возвращает один и тот же мок. Моки записывают, как вы их используете, позволяя вам делать утверждения о том, что ваш код сделал с ними.

MagicMock является подклассом Mock со всеми магическими методами, предварительно созданными и готовыми к использованию. Существуют также варианты без возможности вызова, полезные, когда вы подражаете объектам, которые не могут быть вызваны: NonCallableMock и NonCallableMagicMock.

Декораторы patch() позволяют легко временно заменить классы в определенном модуле объектом Mock. По умолчанию patch() создаст для вас MagicMock. Вы можете указать альтернативный класс Mock, используя аргумент new_callable в patch().

class unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

Создайте новый объект Mock. Mock принимает несколько необязательных аргументов, которые определяют поведение объекта Mock:

  • spec: Это может быть либо список строк, либо существующий объект (класс или экземпляр), который выступает в качестве спецификации для объекта-макета. Если вы передаете объект, то список строк формируется путем вызова dir на объекте (исключая неподдерживаемые магические атрибуты и методы). Доступ к любому атрибуту, не входящему в этот список, вызовет ошибку AttributeError.

    Если spec является объектом (а не списком строк), то __class__ возвращает класс объекта spec. Это позволяет мокам проходить тесты isinstance().

  • spec_set: Более строгий вариант spec. При его использовании попытка set или get атрибута на имитаторе, который не находится на объекте, переданном в качестве spec_set, вызовет ошибку AttributeError.

  • side_effect: Функция, которая будет вызываться каждый раз, когда вызывается Mock. См. атрибут side_effect. Полезно для создания исключений или динамического изменения возвращаемых значений. Функция вызывается с теми же аргументами, что и mock, и если она не возвращает DEFAULT, в качестве возвращаемого значения используется значение этой функции.

    В качестве альтернативы side_effect может быть классом или экземпляром исключения. В этом случае исключение будет вызвано при вызове имитатора.

    Если side_effect является итерируемым, то каждый вызов mock будет возвращать следующее значение из итерируемого.

    Эффект side_effect можно убрать, установив его в значение None.

  • возвращаемое_значение: Значение, возвращаемое при вызове макета. По умолчанию это новый Mock (создается при первом обращении). См. атрибут return_value.

  • unsafe: По умолчанию доступ к любому атрибуту, имя которого начинается с assert, assret, asert, aseert или assrt, вызовет ошибку AttributeError. Передача unsafe=True разрешит доступ к этим атрибутам.

    Добавлено в версии 3.5.

  • обертки: Элемент для обертывания объекта имитатора. Если wraps не None, то вызов Mock передаст вызов обернутому объекту (возвращая реальный результат). Обращение к атрибутам объекта mock вернет объект Mock, который обертывает соответствующий атрибут обернутого объекта (поэтому попытка обращения к несуществующему атрибуту вызовет ошибку AttributeError).

    Если у имитатора явно задано обратное_значение, то вызовы не передаются обернутому объекту, а вместо него возвращается обратное_значение.

  • name: Если у имитатора есть имя, то оно будет использоваться в репрезентации имитатора. Это может быть полезно для отладки. Имя передается дочерним имитаторам.

Макеты также могут быть вызваны с произвольными аргументами в виде ключевых слов. Они будут использоваться для установки атрибутов макета после его создания. Подробнее см. метод configure_mock().

assert_called()

Утверждение, что подражатель был вызван хотя бы один раз.

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called()

Добавлено в версии 3.6.

assert_called_once()

Утверждение, что подражатель был вызван ровно один раз.

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_once()
Traceback (most recent call last):
...
AssertionError: Expected 'method' to have been called once. Called 2 times.

Добавлено в версии 3.6.

assert_called_with(*args, **kwargs)

Этот метод - удобный способ утверждения, что последний вызов был выполнен определенным образом:

>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
assert_called_once_with(*args, **kwargs)

Утверждает, что имитатор был вызван ровно один раз, и этот вызов был с указанными аргументами.

>>> mock = Mock(return_value=None)
>>> mock('foo', bar='baz')
>>> mock.assert_called_once_with('foo', bar='baz')
>>> mock('other', bar='values')
>>> mock.assert_called_once_with('other', bar='values')
Traceback (most recent call last):
  ...
AssertionError: Expected 'mock' to be called once. Called 2 times.
assert_any_call(*args, **kwargs)

подтвердить, что имитатор был вызван с указанными аргументами.

Утверждение проходит, если подражатель всегда вызывался, в отличие от assert_called_with() и assert_called_once_with(), которые проходят только если вызов самый последний, а в случае assert_called_once_with() он также должен быть единственным.

>>> mock = Mock(return_value=None)
>>> mock(1, 2, arg='thing')
>>> mock('some', 'thing', 'else')
>>> mock.assert_any_call(1, 2, arg='thing')
assert_has_calls(calls, any_order=False)

Убедиться, что имитатор был вызван с указанными вызовами. Список mock_calls проверяется на наличие вызовов.

Если any_order равно false, то вызовы должны быть последовательными. Могут быть дополнительные вызовы до или после указанных вызовов.

Если any_order равно true, то вызовы могут быть в любом порядке, но все они должны появляться в mock_calls.

>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)
assert_not_called()

Утверждать, что насмешка не была вызвана.

>>> m = Mock()
>>> m.hello.assert_not_called()
>>> obj = m.hello()
>>> m.hello.assert_not_called()
Traceback (most recent call last):
  ...
AssertionError: Expected 'hello' to not have been called. Called 1 times.

Добавлено в версии 3.5.

reset_mock(*, return_value=False, side_effect=False)

Метод reset_mock сбрасывает все атрибуты вызова для объекта-макета:

>>> mock = Mock(return_value=None)
>>> mock('hello')
>>> mock.called
True
>>> mock.reset_mock()
>>> mock.called
False

Изменено в версии 3.6: В функцию reset_mock добавлен аргумент «только два ключевых слова».

Это может быть полезно, когда вы хотите сделать серию утверждений, в которых повторно используется один и тот же объект. Обратите внимание, что reset_mock() не очищает возвращаемое значение, side_effect или любые дочерние атрибуты, которые вы установили с помощью обычного присваивания по умолчанию. Если вы хотите сбросить обратное_значение или side_effect, то передайте соответствующий параметр как True. Дочерние макеты и макет возвращаемого значения (если таковой имеется) также сбрасываются.

Примечание

обратное_значение, и side_effect являются аргументами только для ключевого слова.

mock_add_spec(spec, spec_set=False)

Добавить спецификацию к макету. spec может быть либо объектом, либо списком строк. Только атрибуты из spec могут быть получены как атрибуты из макета.

Если spec_set равно true, то можно установить только атрибуты спецификации.

attach_mock(mock, attribute)

Прикрепить имитатор в качестве атрибута данного объекта, заменив его имя и родителя. Вызовы присоединенного макета будут записываться в атрибуты method_calls и mock_calls этого макета.

configure_mock(**kwargs)

Установка атрибутов макета с помощью аргументов ключевых слов.

Атрибуты, а также возвращаемые значения и побочные эффекты могут быть установлены на дочерних моках с использованием стандартной точечной нотации и распаковки словаря в вызове метода:

>>> mock = Mock()
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock.configure_mock(**attrs)
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

Того же самого можно добиться при вызове конструктора в моках:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

configure_mock() существует для того, чтобы облегчить настройку после создания макета.

__dir__()

Объекты Mock ограничивают результаты dir(some_mock) полезными результатами. Для макетов с spec это включает все разрешенные атрибуты для макета.

Что делает эта фильтрация и как ее отключить, смотрите в FILTER_DIR.

_get_child_mock(**kw)

Создайте дочерние макеты для атрибутов и возвращаемого значения. По умолчанию дочерние модели будут того же типа, что и родительская. Подклассы Mock могут захотеть переопределить этот параметр, чтобы настроить способ создания дочерних имитаторов.

Для не вызываемых макетов будет использоваться вызываемый вариант (а не любой пользовательский подкласс).

called

Булево значение, показывающее, был ли вызван объект mock или нет:

>>> mock = Mock(return_value=None)
>>> mock.called
False
>>> mock()
>>> mock.called
True
call_count

Целое число, показывающее, сколько раз был вызван объект mock:

>>> mock = Mock(return_value=None)
>>> mock.call_count
0
>>> mock()
>>> mock()
>>> mock.call_count
2
return_value

Установите это значение для настройки значения, возвращаемого при вызове имитатора:

>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'

Возвращаемое значение по умолчанию - это объект mock, и вы можете настроить его обычным образом:

>>> mock = Mock()
>>> mock.return_value.attribute = sentinel.Attribute
>>> mock.return_value()
<Mock name='mock()()' id='...'>
>>> mock.return_value.assert_called_with()

return_value также может быть задан в конструкторе:

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

Это может быть либо функция, которая будет вызываться при вызове имитатора, либо итерабельность, либо исключение (класса или экземпляра), которое будет вызвано.

Если вы передадите функцию, то она будет вызвана с теми же аргументами, что и макет, и если функция не вернет синглтон DEFAULT, то вызов макета вернет все, что вернет функция. Если функция вернет DEFAULT, то макет вернет свое обычное значение (из return_value).

Если вы передаете iterable, он используется для получения итератора, который должен выдавать значение при каждом вызове. Это значение может быть либо экземпляром исключения, которое должно быть вызвано, либо значением, которое должно быть возвращено из вызова имитатора (обработка DEFAULT идентична случаю функции).

Пример макета, который вызывает исключение (для тестирования обработки исключений в API):

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

Использование side_effect для возврата последовательности значений:

>>> mock = Mock()
>>> mock.side_effect = [3, 2, 1]
>>> mock(), mock(), mock()
(3, 2, 1)

Использование вызываемого объекта:

>>> mock = Mock(return_value=3)
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> mock.side_effect = side_effect
>>> mock()
3

side_effect может быть задано в конструкторе. Вот пример, который добавляет единицу к значению, с которым вызывается mock, и возвращает ее:

>>> side_effect = lambda value: value + 1
>>> mock = Mock(side_effect=side_effect)
>>> mock(3)
4
>>> mock(-8)
-7

Установка side_effect в None очищает его:

>>> m = Mock(side_effect=KeyError, return_value=3)
>>> m()
Traceback (most recent call last):
 ...
KeyError
>>> m.side_effect = None
>>> m()
3
call_args

Это либо None (если имитатор не был вызван), либо аргументы, с которыми имитатор был вызван в последний раз. Это будет кортеж: первый член, к которому также можно получить доступ через свойство args, это любые упорядоченные аргументы, с которыми был вызван mock (или пустой кортеж), а второй член, к которому также можно получить доступ через свойство kwargs, это любые аргументы ключевых слов (или пустой словарь).

>>> mock = Mock(return_value=None)
>>> print(mock.call_args)
None
>>> mock()
>>> mock.call_args
call()
>>> mock.call_args == ()
True
>>> mock(3, 4)
>>> mock.call_args
call(3, 4)
>>> mock.call_args == ((3, 4),)
True
>>> mock.call_args.args
(3, 4)
>>> mock.call_args.kwargs
{}
>>> mock(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args
call(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args.args
(3, 4, 5)
>>> mock.call_args.kwargs
{'key': 'fish', 'next': 'w00t!'}

call_args, а также члены списков call_args_list, method_calls и mock_calls являются объектами call. Это кортежи, поэтому их можно распаковать, чтобы получить отдельные аргументы и сделать более сложные утверждения. См. calls as tuples.

Изменено в версии 3.8: Добавлены свойства args и kwargs.

call_args_list

Это список всех вызовов, сделанных к объекту-макету последовательно (поэтому длина списка равна количеству вызовов). Пока не было сделано ни одного вызова, это пустой список. Объект call можно использовать для удобного построения списков вызовов для сравнения с call_args_list.

>>> mock = Mock(return_value=None)
>>> mock()
>>> mock(3, 4)
>>> mock(key='fish', next='w00t!')
>>> mock.call_args_list
[call(), call(3, 4), call(key='fish', next='w00t!')]
>>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)]
>>> mock.call_args_list == expected
True

Членами call_args_list являются объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. calls as tuples.

method_calls

Помимо отслеживания обращений к себе, имитаторы также отслеживают обращения к методам и атрибутам, а также свои методы и атрибуты:

>>> mock = Mock()
>>> mock.method()
<Mock name='mock.method()' id='...'>
>>> mock.property.method.attribute()
<Mock name='mock.property.method.attribute()' id='...'>
>>> mock.method_calls
[call.method(), call.property.method.attribute()]

Членами method_calls являются объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. calls as tuples.

mock_calls

mock_calls записывает все вызовы к объекту mock, его методам, магическим методам и возвращаемым значениям mock.

>>> mock = MagicMock()
>>> result = mock(1, 2, 3)
>>> mock.first(a=3)
<MagicMock name='mock.first()' id='...'>
>>> mock.second()
<MagicMock name='mock.second()' id='...'>
>>> int(mock)
1
>>> result(1)
<MagicMock name='mock()()' id='...'>
>>> expected = [call(1, 2, 3), call.first(a=3), call.second(),
... call.__int__(), call()(1)]
>>> mock.mock_calls == expected
True

Членами mock_calls являются объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. calls as tuples.

Примечание

Способ записи mock_calls означает, что при выполнении вложенных вызовов параметры вызовов-предшественников не записываются и поэтому всегда будут сравниваться одинаково:

>>> mock = MagicMock()
>>> mock.top(a=3).bottom()
<MagicMock name='mock.top().bottom()' id='...'>
>>> mock.mock_calls
[call.top(a=3), call.top().bottom()]
>>> mock.mock_calls[-1] == call.top(a=-1).bottom()
True
__class__

Обычно атрибут __class__ объекта возвращает его тип. Для объекта-макета с атрибутом spec, __class__ вместо него возвращает класс спецификации. Это позволяет объектам-макетам проходить тесты isinstance() для объекта, который они заменяют/маскируют:

>>> mock = Mock(spec=3)
>>> isinstance(mock, int)
True

__class__ является присваиваемым, это позволяет имитатору пройти проверку isinstance(), не заставляя вас использовать спецификацию:

>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True
class unittest.mock.NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs)

Невызываемая версия Mock. Параметры конструктора имеют то же значение, что и в Mock, за исключением return_value и side_effect, которые не имеют значения в невызываемом mock.

Объекты Mock, которые используют класс или экземпляр в качестве spec или spec_set, могут проходить isinstance() тесты:

>>> mock = Mock(spec=SomeClass)
>>> isinstance(mock, SomeClass)
True
>>> mock = Mock(spec_set=SomeClass())
>>> isinstance(mock, SomeClass)
True

Классы Mock имеют поддержку насмешки над магическими методами. Подробную информацию смотрите в magic methods.

Классы mock и декораторы patch() принимают произвольные аргументы ключевых слов для конфигурации. Для декораторов patch() ключевые слова передаются в конструктор создаваемого имитатора. Аргументы ключевых слов предназначены для настройки атрибутов имитатора:

>>> m = MagicMock(attribute=3, other='fish')
>>> m.attribute
3
>>> m.other
'fish'

Возвращаемое значение и побочный эффект дочерних макетов могут быть заданы таким же образом, используя точечную нотацию. Поскольку вы не можете использовать точечные имена непосредственно в вызове, вам придется создать словарь и распаковать его с помощью **:

>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
  ...
KeyError

Вызываемый имитатор, который был создан с помощью spec (или spec_set), будет анализировать сигнатуру объекта спецификации при сопоставлении вызовов имитатора. Таким образом, он может соответствовать аргументам фактического вызова независимо от того, были ли они переданы позиционно или по имени:

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

Это относится к assert_called_with(), assert_called_once_with(), assert_has_calls() и assert_any_call(). Когда Автоспецификация, это также будет применяться к вызовам методов на объекте-макете.

Изменено в версии 3.4: Добавлена интроспекция сигнатур для специфицированных и автоспецифицированных mock-объектов.

class unittest.mock.PropertyMock(*args, **kwargs)

Макет, предназначенный для использования в качестве свойства или другого дескриптора класса. PropertyMock предоставляет методы __get__() и __set__(), чтобы вы могли указать возвращаемое значение, когда оно будет извлечено.

Получение экземпляра PropertyMock из объекта вызывает имитатор без каких-либо аргументов. Установка вызывает имитатор с установленным значением.

>>> class Foo:
...     @property
...     def foo(self):
...         return 'something'
...     @foo.setter
...     def foo(self, value):
...         pass
...
>>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo:
...     mock_foo.return_value = 'mockity-mock'
...     this_foo = Foo()
...     print(this_foo.foo)
...     this_foo.foo = 6
...
mockity-mock
>>> mock_foo.mock_calls
[call(), call(6)]

Из-за способа хранения атрибутов подражания вы не можете напрямую прикрепить PropertyMock к объекту подражания. Вместо этого вы можете прикрепить его к объекту имитационного типа:

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()
class unittest.mock.AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

Асинхронная версия MagicMock. Объект AsyncMock будет вести себя так, чтобы объект распознавался как асинхронная функция, а результат вызова был ожидаемым.

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())  
True

Результат mock() - это асинхронная функция, которая после ожидания будет иметь результат side_effect или return_value:

  • если side_effect является функцией, то асинхронная функция вернет результат этой функции,

  • если side_effect является исключением, асинхронная функция поднимет исключение,

  • если side_effect является итерабельной, асинхронная функция вернет следующее значение итерабельной, однако, если последовательность результатов исчерпана, StopAsyncIteration будет поднята немедленно,

  • если side_effect не определено, функция async вернет значение, определенное return_value, следовательно, по умолчанию функция async возвращает новый объект AsyncMock.

Установка spec функции Mock или MagicMock в асинхронную функцию приведет к тому, что после вызова будет возвращен объект coroutine.

>>> async def async_func(): pass
...
>>> mock = MagicMock(async_func)
>>> mock
<MagicMock spec='function' id='...'>
>>> mock()  
<coroutine object AsyncMockMixin._mock_call at ...>

Установка spec Mock, MagicMock или AsyncMock в класс с асинхронными и синхронными функциями автоматически определит синхронные функции и установит их как MagicMock (если родительский mock является AsyncMock или MagicMock) или Mock (если родительский mock является Mock). Все асинхронные функции будут AsyncMock.

>>> class ExampleClass:
...     def sync_foo():
...         pass
...     async def async_foo():
...         pass
...
>>> a_mock = AsyncMock(ExampleClass)
>>> a_mock.sync_foo
<MagicMock name='mock.sync_foo' id='...'>
>>> a_mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>
>>> mock = Mock(ExampleClass)
>>> mock.sync_foo
<Mock name='mock.sync_foo' id='...'>
>>> mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>

Добавлено в версии 3.8.

assert_awaited()

Утверждение, что имитатор ожидался хотя бы один раз. Обратите внимание, что это происходит отдельно от вызова объекта, необходимо использовать ключевое слово await:

>>> mock = AsyncMock()
>>> async def main(coroutine_mock):
...     await coroutine_mock
...
>>> coroutine_mock = mock()
>>> mock.called
True
>>> mock.assert_awaited()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited.
>>> asyncio.run(main(coroutine_mock))
>>> mock.assert_awaited()
assert_awaited_once()

Утверждение, что насмешка была ожидаема ровно один раз.

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.assert_awaited_once()
>>> asyncio.run(main())
>>> mock.method.assert_awaited_once()
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_awaited_with(*args, **kwargs)

Утверждает, что последний await был с указанными аргументами.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_with('foo', bar='bar')
>>> mock.assert_awaited_with('other')
Traceback (most recent call last):
...
AssertionError: expected call not found.
Expected: mock('other')
Actual: mock('foo', bar='bar')
assert_awaited_once_with(*args, **kwargs)

Утверждает, что мемок был ожидаем ровно один раз и с указанными аргументами.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
>>> asyncio.run(main('foo', bar='bar'))
>>> mock.assert_awaited_once_with('foo', bar='bar')
Traceback (most recent call last):
...
AssertionError: Expected mock to have been awaited once. Awaited 2 times.
assert_any_await(*args, **kwargs)

Утверждает, что мемок был когда-либо ожидаем с указанными аргументами.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> asyncio.run(main('foo', bar='bar'))
>>> asyncio.run(main('hello'))
>>> mock.assert_any_await('foo', bar='bar')
>>> mock.assert_any_await('other')
Traceback (most recent call last):
...
AssertionError: mock('other') await not found
assert_has_awaits(calls, any_order=False)

Утверждает, что имитатор был ожидаем указанными вызовами. Список await_args_list проверяется на наличие ожиданий.

Если any_order равно false, то ожидания должны быть последовательными. Могут быть дополнительные вызовы до или после указанных ожиданий.

Если any_order равно true, то ожидания могут быть расположены в любом порядке, но все они должны появляться в await_args_list.

>>> mock = AsyncMock()
>>> async def main(*args, **kwargs):
...     await mock(*args, **kwargs)
...
>>> calls = [call("foo"), call("bar")]
>>> mock.assert_has_awaits(calls)
Traceback (most recent call last):
...
AssertionError: Awaits not found.
Expected: [call('foo'), call('bar')]
Actual: []
>>> asyncio.run(main('foo'))
>>> asyncio.run(main('bar'))
>>> mock.assert_has_awaits(calls)
assert_not_awaited()

Утверждайте, что насмешек никто не ждал.

>>> mock = AsyncMock()
>>> mock.assert_not_awaited()
reset_mock(*args, **kwargs)

См. Mock.reset_mock(). Также устанавливает await_count в 0, await_args в None, и очищает await_args_list.

await_count

Целое число, отслеживающее, сколько раз объект mock был ожидаем.

>>> mock = AsyncMock()
>>> async def main():
...     await mock()
...
>>> asyncio.run(main())
>>> mock.await_count
1
>>> asyncio.run(main())
>>> mock.await_count
2
await_args

Это либо None (если имитатор не был ожидаем), либо аргументы, с которыми имитатор был ожидаем в последний раз. Функционирует так же, как Mock.call_args.

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args
>>> asyncio.run(main('foo'))
>>> mock.await_args
call('foo')
>>> asyncio.run(main('bar'))
>>> mock.await_args
call('bar')
await_args_list

Это список всех ожиданий, сделанных для объекта-макета по порядку (длина списка равна количеству ожиданий). Пока не было сделано ни одного ожидания, это пустой список.

>>> mock = AsyncMock()
>>> async def main(*args):
...     await mock(*args)
...
>>> mock.await_args_list
[]
>>> asyncio.run(main('foo'))
>>> mock.await_args_list
[call('foo')]
>>> asyncio.run(main('bar'))
>>> mock.await_args_list
[call('foo'), call('bar')]

Звонок на

Макетные объекты можно вызывать. Вызов возвращает значение, заданное в качестве атрибута return_value. Возвращаемое значение по умолчанию - это новый объект Mock; он создается при первом обращении к возвращаемому значению (явно или путем вызова Mock), но сохраняется и возвращается каждый раз один и тот же.

Вызовы, сделанные к объекту, будут записаны в атрибутах типа call_args и call_args_list.

Если установлено значение side_effect, то оно будет вызвано после записи вызова, так что если side_effect вызовет исключение, вызов все равно будет записан.

Самый простой способ заставить имитатор поднимать исключение при вызове - это сделать side_effect классом или экземпляром исключения:

>>> m = MagicMock(side_effect=IndexError)
>>> m(1, 2, 3)
Traceback (most recent call last):
  ...
IndexError
>>> m.mock_calls
[call(1, 2, 3)]
>>> m.side_effect = KeyError('Bang!')
>>> m('two', 'three', 'four')
Traceback (most recent call last):
  ...
KeyError: 'Bang!'
>>> m.mock_calls
[call(1, 2, 3), call('two', 'three', 'four')]

Если side_effect является функцией, то все, что возвращает эта функция, возвращает и вызов имитатора. Функция side_effect вызывается с теми же аргументами, что и имитатор. Это позволяет динамически изменять возвращаемое значение вызова в зависимости от входных данных:

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

Если вы хотите, чтобы имитатор по-прежнему возвращал значение по умолчанию (новый имитатор) или любое заданное значение, то это можно сделать двумя способами. Либо вернуть mock.return_value изнутри side_effect, либо вернуть DEFAULT:

>>> m = MagicMock()
>>> def side_effect(*args, **kwargs):
...     return m.return_value
...
>>> m.side_effect = side_effect
>>> m.return_value = 3
>>> m()
3
>>> def side_effect(*args, **kwargs):
...     return DEFAULT
...
>>> m.side_effect = side_effect
>>> m()
3

Чтобы удалить side_effect и вернуться к поведению по умолчанию, установите side_effect на None:

>>> m = MagicMock(return_value=6)
>>> def side_effect(*args, **kwargs):
...     return 3
...
>>> m.side_effect = side_effect
>>> m()
3
>>> m.side_effect = None
>>> m()
6

side_effect также может быть любым итерируемым объектом. Повторные вызовы mock будут возвращать значения из итерируемого объекта (пока итерируемый объект не будет исчерпан и не возникнет ошибка StopIteration):

>>> m = MagicMock(side_effect=[1, 2, 3])
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
  ...
StopIteration

Если какие-либо члены iterable являются исключениями, они будут подняты, а не возвращены:

>>> iterable = (33, ValueError, 66)
>>> m = MagicMock(side_effect=iterable)
>>> m()
33
>>> m()
Traceback (most recent call last):
 ...
ValueError
>>> m()
66

Удаление атрибутов

Макетные объекты создают атрибуты по требованию. Это позволяет им притворяться объектами любого типа.

Вы можете захотеть, чтобы объект-макет возвращал False на вызов hasattr() или поднимал AttributeError при получении атрибута. Вы можете сделать это, предоставив объект в качестве spec для имитатора, но это не всегда удобно.

Вы «блокируете» атрибуты, удаляя их. После удаления доступ к атрибуту вызовет ошибку AttributeError.

>>> mock = MagicMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
    ...
AttributeError: f

Макетные имена и атрибут name

Поскольку «name» является аргументом конструктора Mock, если вы хотите, чтобы у вашего объекта-макета был атрибут «name», вы не можете просто передать его во время создания. Есть две альтернативы. Один вариант - использовать configure_mock():

>>> mock = MagicMock()
>>> mock.configure_mock(name='my_name')
>>> mock.name
'my_name'

Более простой вариант - просто установить атрибут «name» после создания макета:

>>> mock = MagicMock()
>>> mock.name = "foo"

Присоединение блоков в качестве атрибутов

Когда вы присоединяете имитатор в качестве атрибута другого имитатора (или в качестве возвращаемого значения), он становится «дочерним» по отношению к этому имитатору. Вызовы к дочернему объекту записываются в атрибуты method_calls и mock_calls родительского объекта. Это полезно для настройки дочерних имитаторов и последующего присоединения их к родительскому, или для присоединения имитаторов к родительскому, который записывает все вызовы дочерних и позволяет делать утверждения о порядке вызовов между имитаторами:

>>> parent = MagicMock()
>>> child1 = MagicMock(return_value=None)
>>> child2 = MagicMock(return_value=None)
>>> parent.child1 = child1
>>> parent.child2 = child2
>>> child1(1)
>>> child2(2)
>>> parent.mock_calls
[call.child1(1), call.child2(2)]

Исключением является случай, когда у подражателя есть имя. Это позволяет предотвратить «воспитание», если по какой-то причине вы не хотите, чтобы оно происходило.

>>> mock = MagicMock()
>>> not_a_child = MagicMock(name='not-a-child')
>>> mock.attribute = not_a_child
>>> mock.attribute()
<MagicMock name='not-a-child()' id='...'>
>>> mock.mock_calls
[]

Макеты, созданные для вас с помощью patch(), автоматически получают имена. Чтобы прикрепить макеты, имеющие имена, к родителю, вы используете метод attach_mock():

>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
...     with patch('__main__.thing2', return_value=None) as child2:
...         parent.attach_mock(child1, 'child1')
...         parent.attach_mock(child2, 'child2')
...         child1('one')
...         child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]
1

Единственным исключением являются магические методы и атрибуты (те, которые имеют ведущее и последующее двойное подчеркивание). Mock не создает их, а вместо этого выдает сообщение AttributeError. Это происходит потому, что интерпретатор часто неявно запрашивает эти методы и очень* путается, получая новый объект Mock, когда ожидает магический метод. Если вам нужна поддержка магических методов, смотрите magic methods.

Патчеры

Декораторы исправлений используются для исправления объектов только в пределах области действия функции, которую они украшают. Они автоматически выполняют распаковку за вас, даже если при этом возникают исключения. Все эти функции также можно использовать в операторах with или в качестве декораторов классов.

патч

Примечание

Главное - выполнять исправления в правильном пространстве имен. См. раздел where to patch.

unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

patch() действует как декоратор функций, декоратор классов или менеджер контекста. Внутри тела функции или оператора with, цель исправляется на новый объект. При выходе из функции или оператора with исправление отменяется.

Если new опущено, то target заменяется на AsyncMock, если исправляемый объект является асинхронной функцией, или MagicMock в противном случае. Если patch() используется в качестве декоратора и new опущено, то созданный mock передается в качестве дополнительного аргумента декорируемой функции. Если patch() используется в качестве менеджера контекста, созданный макет возвращается менеджером контекста.

target должна быть строкой в форме 'package.module.ClassName'. Объект target импортируется, а указанный объект заменяется объектом new, поэтому target должен быть импортируемым из среды, из которой вы вызываете patch(). Цель импортируется при выполнении декорированной функции, а не во время декорирования.

Аргументы ключевых слов spec и spec_set передаются в MagicMock, если patch создает его для вас.

Дополнительно вы можете передать spec=True или spec_set=True, что заставит patch передать в качестве объекта spec/spec_set объект, над которым издеваются.

new_callable позволяет вам указать другой класс или вызываемый объект, который будет вызван для создания нового объекта. По умолчанию AsyncMock используется для асинхронных функций и MagicMock для остальных.

Более мощной формой spec является autospec. Если вы установите значение autospec=True, то макет будет создан со спецификацией заменяемого объекта. Все атрибуты имитатора также будут иметь спецификацию соответствующего атрибута заменяемого объекта. У методов и функций, над которыми подражают, будут проверяться аргументы, и если они будут вызваны с неправильной сигнатурой, то будет выдано сообщение TypeError. Для макетов, заменяющих класс, их возвращаемое значение («экземпляр») будет иметь ту же спецификацию, что и класс. См. функции create_autospec() и Автоспецификация.

Вместо autospec=True можно передать autospec=some_object, чтобы использовать произвольный объект в качестве спецификации вместо заменяемого.

По умолчанию patch() не заменяет несуществующие атрибуты. Если вы передадите create=True, и атрибут не существует, patch создаст атрибут для вас при вызове исправляемой функции, и удалит его снова после выхода исправляемой функции. Это полезно для написания тестов против атрибутов, которые ваш производственный код создает во время выполнения. По умолчанию она выключена, так как это может быть опасно. Включив его, вы можете писать проходные тесты против API, которых на самом деле не существует!

Примечание

Изменено в версии 3.5: Если вы патчите встроенные модули в модуле, то вам не нужно передавать create=True, они будут добавлены по умолчанию.

Patch можно использовать как декоратор класса TestCase. Он работает путем декорирования каждого тестового метода в классе. Это уменьшает количество кода, когда ваши тестовые методы имеют общий набор патчей. patch() находит тесты, ища имена методов, которые начинаются с patch.TEST_PREFIX. По умолчанию это 'test', что соответствует способу поиска тестов unittest. Вы можете указать альтернативный префикс, задав patch.TEST_PREFIX.

Patch можно использовать в качестве менеджера контекста с помощью оператора with. В данном случае исправление применяется к блоку с отступом после оператора with. Если вы используете «as», то исправленный объект будет привязан к имени после «as»; очень полезно, если patch() создает для вас объект-макет.

patch() принимает произвольные аргументы в виде ключевых слов. Они будут переданы в AsyncMock, если исправляемый объект является асинхронным, в MagicMock в противном случае или в new_callable, если указано.

patch.dict(...), patch.multiple(...) и patch.object(...) доступны для альтернативных случаев использования.

patch() как декоратор функции, создавая для вас имитатор и передавая его в декорируемую функцию:

>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
...     print(mock_class is SomeClass)
...
>>> function(None)
True

Патчтинг класса заменяет класс на MagicMock экземпляр. Если класс инстанцирован в тестируемом коде, то будет использоваться return_value экземпляр.

Если класс инстанцируется несколько раз, можно использовать side_effect, чтобы каждый раз возвращать новый mock. В качестве альтернативы вы можете установить возвращаемое_значение как угодно.

Чтобы настроить возвращаемые значения для методов экземпляров в исправленном классе, вы должны сделать это на return_value. Например:

>>> class Class:
...     def method(self):
...         pass
...
>>> with patch('__main__.Class') as MockClass:
...     instance = MockClass.return_value
...     instance.method.return_value = 'foo'
...     assert Class() is instance
...     assert Class().method() == 'foo'
...

Если вы используете spec или spec_set и patch() заменяет класс, то возвращаемое значение созданного mock будет иметь тот же spec.

>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()

Аргумент new_callable полезен, когда вы хотите использовать класс, альтернативный классу по умолчанию MagicMock для создаваемого имитатора. Например, если вы хотите, чтобы использовался NonCallableMock:

>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
...     assert thing is mock_thing
...     thing()
...
Traceback (most recent call last):
  ...
TypeError: 'NonCallableMock' object is not callable

Другим вариантом использования может быть замена объекта на экземпляр io.StringIO:

>>> from io import StringIO
>>> def foo():
...     print('Something')
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

Когда patch() создает для вас имитатор, обычно первое, что вам нужно сделать, это настроить имитатор. Некоторые из этих настроек могут быть выполнены в вызове патча. Любые произвольные ключевые слова, которые вы передадите в вызов, будут использованы для установки атрибутов созданного макета:

>>> patcher = patch('__main__.thing', first='one', second='two')
>>> mock_thing = patcher.start()
>>> mock_thing.first
'one'
>>> mock_thing.second
'two'

Помимо атрибутов созданного макета можно настроить атрибуты дочерних макетов, такие как return_value и side_effect. Синтаксически их нельзя передавать непосредственно в качестве аргументов ключевых слов, но словарь с такими ключами может быть расширен в вызов patch() с помощью **:

>>> config = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> patcher = patch('__main__.thing', **config)
>>> mock_thing = patcher.start()
>>> mock_thing.method()
3
>>> mock_thing.other()
Traceback (most recent call last):
  ...
KeyError

По умолчанию попытка патча несуществующей функции в модуле (или метода или атрибута в классе) завершится неудачей с сообщением AttributeError:

>>> @patch('sys.non_existing_attribute', 42)
... def test():
...     assert sys.non_existing_attribute == 42
...
>>> test()
Traceback (most recent call last):
  ...
AttributeError: <module 'sys' (built-in)> does not have the attribute 'non_existing_attribute'

но добавление create=True в вызов patch() заставит предыдущий пример работать так, как ожидалось:

>>> @patch('sys.non_existing_attribute', 42, create=True)
... def test(mock_stdout):
...     assert sys.non_existing_attribute == 42
...
>>> test()

Изменено в версии 3.8: patch() теперь возвращает AsyncMock, если цель является асинхронной функцией.

патч.объект

patch.object(target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

сопоставить именованный член (атрибут) объекта (цель) с объектом-макетом.

patch.object() может использоваться как декоратор, декоратор класса или менеджер контекста. Аргументы new, spec, create, spec_set, autospec и new_callable имеют то же значение, что и для patch(). Как и patch(), patch.object() принимает произвольные аргументы ключевых слов для настройки создаваемого имитационного объекта.

При использовании в качестве декоратора класса patch.object() отдает честь patch.TEST_PREFIX в выборе методов для обертывания.

Вы можете вызвать patch.object() с тремя аргументами или с двумя аргументами. Форма с тремя аргументами принимает объект для исправления, имя атрибута и объект, на который нужно заменить атрибут.

При вызове в форме с двумя аргументами вы опускаете заменяющий объект, и для вас создается макет, который передается в качестве дополнительного аргумента декорированной функции:

>>> @patch.object(SomeClass, 'class_method')
... def test(mock_method):
...     SomeClass.class_method(3)
...     mock_method.assert_called_with(3)
...
>>> test()

spec, create и другие аргументы для patch.object() имеют то же значение, что и для patch().

patch.dict

patch.dict(in_dict, values=(), clear=False, **kwargs)

Исправьте словарь или объект, похожий на словарь, и восстановите словарь в исходное состояние после проверки.

in_dict может быть словарем или отображением, как контейнер. Если это отображение, то оно должно как минимум поддерживать получение, установку и удаление элементов, а также итерацию по ключам.

in_dict также может быть строкой, указывающей имя словаря, который будет получен при его импорте.

values может быть словарем значений для установки в словарь. values также может быть итерабельной системой пар (key, value).

Если clear равно true, то словарь будет очищен перед установкой новых значений.

patch.dict() может также вызываться с произвольными аргументами в виде ключевых слов для установки значений в словаре.

Изменено в версии 3.8: patch.dict() теперь возвращает исправленный словарь при использовании в качестве менеджера контекста.

patch.dict() может использоваться как менеджер контекста, декоратор или декоратор класса:

>>> foo = {}
>>> @patch.dict(foo, {'newkey': 'newvalue'})
... def test():
...     assert foo == {'newkey': 'newvalue'}
>>> test()
>>> assert foo == {}

При использовании в качестве декоратора класса patch.dict() отдает предпочтение patch.TEST_PREFIX (по умолчанию 'test') при выборе методов для обертывания:

>>> import os
>>> import unittest
>>> from unittest.mock import patch
>>> @patch.dict('os.environ', {'newkey': 'newvalue'})
... class TestSample(unittest.TestCase):
...     def test_sample(self):
...         self.assertEqual(os.environ['newkey'], 'newvalue')

Если вы хотите использовать другой префикс для своего теста, вы можете сообщить патчерам о другом префиксе, установив значение patch.TEST_PREFIX. Подробнее о том, как изменить значение смотрите в TEST_PREFIX.

patch.dict() можно использовать для добавления членов в словарь, или просто позволить тесту изменить словарь, и убедиться, что словарь будет восстановлен после завершения теста.

>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}) as patched_foo:
...     assert foo == {'newkey': 'newvalue'}
...     assert patched_foo == {'newkey': 'newvalue'}
...     # You can add, update or delete keys of foo (or patched_foo, it's the same dict)
...     patched_foo['spam'] = 'eggs'
...
>>> assert foo == {}
>>> assert patched_foo == {}
>>> import os
>>> with patch.dict('os.environ', {'newkey': 'newvalue'}):
...     print(os.environ['newkey'])
...
newvalue
>>> assert 'newkey' not in os.environ

Ключевые слова могут быть использованы в вызове patch.dict() для установки значений в словаре:

>>> mymodule = MagicMock()
>>> mymodule.function.return_value = 'fish'
>>> with patch.dict('sys.modules', mymodule=mymodule):
...     import mymodule
...     mymodule.function('some', 'args')
...
'fish'

patch.dict() можно использовать с объектами типа словарей, которые на самом деле словарями не являются. Как минимум, они должны поддерживать получение, установку, удаление элементов и либо итерацию, либо проверку принадлежности. Это соответствует магическим методам __getitem__(), __setitem__(), __delitem__() и либо __iter__(), либо __contains__().

>>> class Container:
...     def __init__(self):
...         self.values = {}
...     def __getitem__(self, name):
...         return self.values[name]
...     def __setitem__(self, name, value):
...         self.values[name] = value
...     def __delitem__(self, name):
...         del self.values[name]
...     def __iter__(self):
...         return iter(self.values)
...
>>> thing = Container()
>>> thing['one'] = 1
>>> with patch.dict(thing, one=2, two=3):
...     assert thing['one'] == 2
...     assert thing['two'] == 3
...
>>> assert thing['one'] == 1
>>> assert list(thing) == ['one']

patch.multiple

patch.multiple(target, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

Выполнить несколько исправлений за один вызов. Он принимает объект для исправления (либо как объект, либо как строку для получения объекта путем импорта) и аргументы ключевых слов для исправлений:

with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'):
    ...

Используйте DEFAULT в качестве значения, если вы хотите, чтобы patch.multiple() создавал макеты за вас. В этом случае созданные макеты передаются в декорированную функцию по ключевому слову, а словарь возвращается, когда patch.multiple() используется в качестве менеджера контекста.

patch.multiple() может использоваться как декоратор, декоратор класса или менеджер контекста. Аргументы spec, spec_set, create, autospec и new_callable имеют то же значение, что и для patch(). Эти аргументы будут применяться ко всеми исправлениям, выполненным patch.multiple().

При использовании в качестве декоратора класса patch.multiple() отдает честь patch.TEST_PREFIX в выборе методов для обертывания.

Если вы хотите, чтобы patch.multiple() создавал макеты за вас, то вы можете использовать DEFAULT в качестве значения. Если вы используете patch.multiple() в качестве декоратора, то созданные макеты передаются в декорируемую функцию по ключевому слову.

>>> thing = object()
>>> other = object()

>>> @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(thing, other):
...     assert isinstance(thing, MagicMock)
...     assert isinstance(other, MagicMock)
...
>>> test_function()

patch.multiple() может быть вложен с другими декораторами patch, но помещает аргументы, передаваемые ключевым словом, после любого из стандартных аргументов, создаваемых patch():

>>> @patch('sys.exit')
... @patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
... def test_function(mock_exit, other, thing):
...     assert 'other' in repr(other)
...     assert 'thing' in repr(thing)
...     assert 'exit' in repr(mock_exit)
...
>>> test_function()

Если в качестве менеджера контекста используется patch.multiple(), то значение, возвращаемое менеджером контекста, представляет собой словарь, в котором созданные макеты имеют ключи по имени:

>>> with patch.multiple('__main__', thing=DEFAULT, other=DEFAULT) as values:
...     assert 'other' in repr(values['other'])
...     assert 'thing' in repr(values['thing'])
...     assert values['thing'] is thing
...     assert values['other'] is other
...

методы исправления: запуск и остановка

Все патчеры имеют методы start() и stop(). Они упрощают выполнение исправлений в методах setUp или там, где вы хотите выполнить несколько исправлений без вложенных декораторов или с помощью операторов.

Для их использования вызовите patch(), patch.object() или patch.dict() как обычно и сохраните ссылку на возвращаемый объект patcher. Затем вы можете вызвать start() для установки заплаты на место и stop() для ее отмены.

Если вы используете patch() для создания макета, то он будет возвращен вызовом patcher.start.

>>> patcher = patch('package.module.ClassName')
>>> from package import module
>>> original = module.ClassName
>>> new_mock = patcher.start()
>>> assert module.ClassName is not original
>>> assert module.ClassName is new_mock
>>> patcher.stop()
>>> assert module.ClassName is original
>>> assert module.ClassName is not new_mock

Типичным примером может быть выполнение нескольких исправлений в методе setUp в методе TestCase:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         self.patcher1 = patch('package.module.Class1')
...         self.patcher2 = patch('package.module.Class2')
...         self.MockClass1 = self.patcher1.start()
...         self.MockClass2 = self.patcher2.start()
...
...     def tearDown(self):
...         self.patcher1.stop()
...         self.patcher2.stop()
...
...     def test_something(self):
...         assert package.module.Class1 is self.MockClass1
...         assert package.module.Class2 is self.MockClass2
...
>>> MyTest('test_something').run()

Осторожно

Если вы используете эту технику, вы должны убедиться, что исправление «отменено» вызовом stop. Это может быть сложнее, чем вы думаете, потому что если в setUp возникает исключение, то tearDown не вызывается. unittest.TestCase.addCleanup() делает это проще:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         patcher = patch('package.module.Class')
...         self.MockClass = patcher.start()
...         self.addCleanup(patcher.stop)
...
...     def test_something(self):
...         assert package.module.Class is self.MockClass
...

В качестве дополнительного бонуса вам больше не нужно хранить ссылку на объект patcher.

Также можно остановить все запущенные патчи, используя patch.stopall().

patch.stopall()

Остановить все активные патчи. Останавливает только патчи, запущенные с помощью start.

встраиваемые модули

Вы можете исправлять любые встроенные модули внутри модуля. Следующий пример исправляет встроенный модуль ord():

>>> @patch('__main__.ord')
... def test(mock_ord):
...     mock_ord.return_value = 101
...     print(ord('c'))
...
>>> test()
101

TEST_PREFIX

Все патчеры могут быть использованы в качестве декораторов класса. При таком использовании они оборачивают каждый тестовый метод класса. Патчеры распознают методы, начинающиеся с 'test', как тестовые. Это тот же способ, которым unittest.TestLoader находит тестовые методы по умолчанию.

Возможно, вы захотите использовать другой префикс для своих тестов. Вы можете сообщить патчерам о другом префиксе, установив patch.TEST_PREFIX:

>>> patch.TEST_PREFIX = 'foo'
>>> value = 3
>>>
>>> @patch('__main__.value', 'not three')
... class Thing:
...     def foo_one(self):
...         print(value)
...     def foo_two(self):
...         print(value)
...
>>>
>>> Thing().foo_one()
not three
>>> Thing().foo_two()
not three
>>> value
3

Декораторы матрешки

Если вы хотите выполнить несколько исправлений, вы можете просто сложить декораторы.

С помощью этого узора можно сложить несколько накладных декоративных элементов:

>>> @patch.object(SomeClass, 'class_method')
... @patch.object(SomeClass, 'static_method')
... def test(mock1, mock2):
...     assert SomeClass.static_method is mock1
...     assert SomeClass.class_method is mock2
...     SomeClass.static_method('foo')
...     SomeClass.class_method('bar')
...     return mock1, mock2
...
>>> mock1, mock2 = test()
>>> mock1.assert_called_once_with('foo')
>>> mock2.assert_called_once_with('bar')

Обратите внимание, что декораторы применяются снизу вверх. Это стандартный способ применения декораторов в Python. Порядок созданных макетов, переданных в вашу тестовую функцию, соответствует этому порядку.

Где ставить заплатки

patch() работает путем (временной) замены объекта, на который указывает имя name, на другой объект. На любой отдельный объект может указывать множество имен, поэтому для того, чтобы исправление работало, вы должны убедиться, что исправляете имя, используемое тестируемой системой.

Основной принцип заключается в том, что вы ставите заплатку там, где объект находится, что не обязательно является тем же местом, где он определен. Пара примеров поможет прояснить это.

Представьте, что у нас есть проект, который мы хотим протестировать, со следующей структурой:

a.py
    -> Defines SomeClass

b.py
    -> from a import SomeClass
    -> some_function instantiates SomeClass

Теперь мы хотим протестировать some_function, но мы хотим высмеять SomeClass, используя patch(). Проблема в том, что когда мы импортируем модуль b, что нам придется сделать, он импортирует SomeClass из модуля a. Если мы используем patch(), чтобы высмеять a.SomeClass, это никак не повлияет на наш тест; модуль b уже имеет ссылку на реальный SomeClass, и похоже, что наши исправления не повлияли.

Ключевым моментом является исправление SomeClass там, где он используется (или где он просматривается). В данном случае some_function будет искать SomeClass в модуле b, куда мы его импортировали. Патчирование должно выглядеть следующим образом:

@patch('b.SomeClass')

Однако рассмотрим альтернативный сценарий, когда вместо from a import SomeClass модуль b делает import a, а some_function использует a.SomeClass. Обе эти формы импорта являются общими. В этом случае класс, который мы хотим патчить, ищется в модуле, поэтому вместо него мы должны патчить a.SomeClass:

@patch('a.SomeClass')

Дескрипторы исправлений и прокси-объекты

И patch, и patch.object корректно исправляют и восстанавливают дескрипторы: методы класса, статические методы и свойства. Их следует исправлять на классе, а не на экземпляре. Они также работают с некоторыми объектами, которые проксируют доступ к атрибутам, например, django settings object.

MagicMock и поддержка магических методов

Издевательство над магическими методами

Mock поддерживает имитацию методов протокола Python, также известных как «магические методы». Это позволяет подражающим объектам заменять контейнеры или другие объекты, реализующие протоколы Python.

Поскольку магические методы ищутся иначе, чем обычные методы 2, эта поддержка была реализована специально. Это означает, что поддерживаются только определенные магические методы. Список поддерживаемых методов включает почти все из них. Если вам не хватает каких-то методов, пожалуйста, сообщите нам об этом.

Вы высмеиваете магические методы, устанавливая интересующий вас метод в качестве функции или высмеиваемого экземпляра. Если вы используете функцию, то она должна принимать self в качестве первого аргумента 3.

>>> def __str__(self):
...     return 'fooble'
...
>>> mock = Mock()
>>> mock.__str__ = __str__
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__str__ = Mock()
>>> mock.__str__.return_value = 'fooble'
>>> str(mock)
'fooble'
>>> mock = Mock()
>>> mock.__iter__ = Mock(return_value=iter([]))
>>> list(mock)
[]

Один из примеров использования этого - объекты мокинга, используемые в качестве менеджеров контекста в операторе with:

>>> mock = Mock()
>>> mock.__enter__ = Mock(return_value='foo')
>>> mock.__exit__ = Mock(return_value=False)
>>> with mock as m:
...     assert m == 'foo'
...
>>> mock.__enter__.assert_called_with()
>>> mock.__exit__.assert_called_with(None, None, None)

Вызовы магических методов не отображаются в method_calls, но они записываются в mock_calls.

Примечание

Если вы используете ключевой аргумент spec для создания макета, то попытка установить магический метод, которого нет в спецификации, вызовет ошибку AttributeError.

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

  • __hash__, __sizeof__, __repr__ и __str__.

  • __dir__, __format__ и __subclasses__.

  • __round__, __floor__, __trunc__ и __ceil__.

  • Сравнения: __lt__, __gt__, __le__, __ge__, __eq__ и __ne__.

  • Контейнерные методы: __getitem__, __setitem__, __delitem__, __contains__, __len__, __iter__, __reversed__ и __missing__.

  • Контекстный менеджер: __enter__, __exit__, __aenter__ и __aexit__

  • Унарные числовые методы: __neg__, __pos__ и __invert__

  • Числовые методы (включая варианты правой руки и in-place): __add__, __sub__, __mul__, __matmul__, __div__, __truediv__, __floordiv__, __mod__, __divmod__, __lshift__, __rshift__, __and__, __xor__, __or__ и __pow__.

  • Методы преобразования чисел: __complex__, __int__, __float__ и __index__.

  • Методы дескриптора: __get__, __set__ и __delete__

  • Маринование: __reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate__ и __setstate__.

  • Представление пути файловой системы: __fspath__

  • Асинхронные методы итерации: __aiter__ и __anext__.

Изменено в версии 3.8: Добавлена поддержка os.PathLike.__fspath__().

Изменено в версии 3.8: Добавлена поддержка __aenter__, __aexit__, __aiter__ и __anext__.

Следующие методы существуют, но не поддерживаются, поскольку они либо используются mock, либо не могут быть установлены динамически, либо могут вызвать проблемы:

  • __getattr__, __setattr__, __init__ и __new__.

  • __prepare__, __instancecheck__, __subclasscheck__, __del__

Волшебная насмешка

Существует два варианта MagicMock: MagicMock и NonCallableMagicMock.

class unittest.mock.MagicMock(*args, **kw)

MagicMock является подклассом Mock с реализацией по умолчанию большинства магических методов. Вы можете использовать MagicMock без необходимости самостоятельно настраивать магические методы.

Параметры конструктора имеют то же значение, что и для Mock.

Если вы используете аргументы spec или spec_set, то будут созданы только магические методы, существующие в спецификации.

class unittest.mock.NonCallableMagicMock(*args, **kw)

Не вызываемая версия MagicMock.

Параметры конструктора имеют то же значение, что и для MagicMock, за исключением return_value и side_effect, которые не имеют значения для невызываемого mock.

Магические методы настраиваются с помощью объектов MagicMock, поэтому вы можете настроить их и использовать обычным способом:

>>> mock = MagicMock()
>>> mock[3] = 'fish'
>>> mock.__setitem__.assert_called_with(3, 'fish')
>>> mock.__getitem__.return_value = 'result'
>>> mock[2]
'result'

По умолчанию многие методы протокола должны возвращать объекты определенного типа. Эти методы предварительно настроены с возвращаемым значением по умолчанию, так что они могут использоваться без необходимости делать что-либо, если вас не интересует возвращаемое значение. Вы все еще можете установить возвращаемое значение вручную, если хотите изменить значение по умолчанию.

Методы и их значения по умолчанию:

  • __lt__: NotImplemented

  • __gt__: NotImplemented

  • __le__: NotImplemented

  • __ge__: NotImplemented

  • __int__: 1

  • __contains__: False

  • __len__: 0

  • __iter__: iter([])

  • __exit__: False

  • __aexit__: False

  • __complex__: 1j

  • __float__: 1.0

  • __bool__: True

  • __index__: 1

  • __hash__: хэш по умолчанию для подражания

  • __str__: строка по умолчанию для насмешки

  • __sizeof__: размер по умолчанию для макета

Например:

>>> mock = MagicMock()
>>> int(mock)
1
>>> len(mock)
0
>>> list(mock)
[]
>>> object() in mock
False

Два метода равенства, __eq__() и __ne__(), являются специальными. Они выполняют стандартное сравнение равенства по тождеству, используя атрибут side_effect, если вы не измените их возвращаемое значение, чтобы вернуть что-то другое:

>>> MagicMock() == 3
False
>>> MagicMock() != 3
True
>>> mock = MagicMock()
>>> mock.__eq__.return_value = True
>>> mock == 3
True

Возвращаемое значение MagicMock.__iter__() может быть любым итерируемым объектом и не обязательно должно быть итератором:

>>> mock = MagicMock()
>>> mock.__iter__.return_value = ['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
['a', 'b', 'c']

Если возвращаемое значение является итератором, то при однократной итерации он будет использован, а при последующих итерациях будет получен пустой список:

>>> mock.__iter__.return_value = iter(['a', 'b', 'c'])
>>> list(mock)
['a', 'b', 'c']
>>> list(mock)
[]

MagicMock настроены все поддерживаемые магические методы, за исключением некоторых неясных и устаревших. Вы все еще можете настроить их, если хотите.

Магические методы, которые поддерживаются, но не установлены по умолчанию в MagicMock, следующие:

  • __subclasses__

  • __dir__

  • __format__

  • __get__, __set__ и __delete__.

  • __reversed__ и __missing__.

  • __reduce__, __reduce_ex__, __getinitargs__, __getnewargs__, __getstate__ и __setstate__.

  • __getformat__ и __setformat__.

2

Магические методы должны искаться в классе, а не в экземпляре. Различные версии Python непоследовательно применяют это правило. Поддерживаемые протокольные методы должны работать со всеми поддерживаемыми версиями Python.

3

Функция, по сути, подключается к классу, но каждый экземпляр Mock хранится изолированно от других.

Помощники

сентинел

unittest.mock.sentinel

Объект sentinel обеспечивает удобный способ предоставления уникальных объектов для ваших тестов.

Атрибуты создаются по требованию, когда вы обращаетесь к ним по имени. Обращение к одному и тому же атрибуту всегда будет возвращать один и тот же объект. Возвращаемые объекты имеют разумный repr, чтобы сообщения об ошибках при тестировании были читаемы.

Изменено в версии 3.7: Атрибуты sentinel теперь сохраняют свою идентичность, когда они copied или pickled.

Иногда при тестировании необходимо проверить, что определенный объект передается в качестве аргумента другому методу или возвращается. Для проверки этого обычно создаются именованные объекты sentinel. sentinel предоставляет удобный способ создания и проверки идентичности таких объектов.

В этом примере обезьяний патч method возвращает sentinel.some_object:

>>> real = ProductionClass()
>>> real.method = Mock(name="method")
>>> real.method.return_value = sentinel.some_object
>>> result = real.method()
>>> assert result is sentinel.some_object
>>> result
sentinel.some_object

DEFAULT

unittest.mock.DEFAULT

Объект DEFAULT - это предварительно созданный сентинел (на самом деле sentinel.DEFAULT). Он может использоваться функциями side_effect для указания того, что должно использоваться обычное возвращаемое значение.

звоните на

unittest.mock.call(*args, **kwargs)

call() - это вспомогательный объект для создания более простых утверждений, для сравнения с call_args, call_args_list, mock_calls и method_calls. call() также может использоваться с assert_has_calls().

>>> m = MagicMock(return_value=None)
>>> m(1, 2, a='foo', b='bar')
>>> m()
>>> m.call_args_list == [call(1, 2, a='foo', b='bar'), call()]
True
call.call_list()

Для объекта вызова, который представляет несколько вызовов, call_list() возвращает список всех промежуточных вызовов, а также конечный вызов.

call_list особенно полезен для утверждений о «цепных вызовах». Цепной вызов - это несколько вызовов в одной строке кода. Это приводит к нескольким записям в mock_calls на макете. Ручное построение последовательности вызовов может быть утомительным.

call_list() может построить последовательность вызовов из одного и того же цепного вызова:

>>> m = MagicMock()
>>> m(1).method(arg='foo').other('bar')(2.0)
<MagicMock name='mock().method().other()()' id='...'>
>>> kall = call(1).method(arg='foo').other('bar')(2.0)
>>> kall.call_list()
[call(1),
 call().method(arg='foo'),
 call().method().other('bar'),
 call().method().other()(2.0)]
>>> m.mock_calls == kall.call_list()
True

Объект call представляет собой либо кортеж из (позиционные args, ключевые args), либо (имя, позиционные args, ключевые args) в зависимости от того, как он был построен. Когда вы создаете их сами, это не особенно интересно, но объекты call, которые находятся в атрибутах Mock.call_args, Mock.call_args_list и Mock.mock_calls, могут быть проанализированы, чтобы получить отдельные аргументы, которые они содержат.

Объекты call в Mock.call_args и Mock.call_args_list представляют собой два кортежа (позиционные args, ключевые args), тогда как объекты call в Mock.mock_calls, а также те, которые вы конструируете сами, представляют собой три кортежа (имя, позиционные args, ключевые args).

Вы можете использовать их «кортежность» для извлечения отдельных аргументов для более сложного анализа и утверждений. Позиционные аргументы представляют собой кортеж (пустой кортеж, если нет позиционных аргументов), а аргументы ключевых слов - словарь:

>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> kall.args
(1, 2, 3)
>>> kall.kwargs
{'arg': 'one', 'arg2': 'two'}
>>> kall.args is kall[0]
True
>>> kall.kwargs is kall[1]
True
>>> m = MagicMock()
>>> m.foo(4, 5, 6, arg='two', arg2='three')
<MagicMock name='mock.foo()' id='...'>
>>> kall = m.mock_calls[0]
>>> name, args, kwargs = kall
>>> name
'foo'
>>> args
(4, 5, 6)
>>> kwargs
{'arg': 'two', 'arg2': 'three'}
>>> name is m.mock_calls[0][0]
True

create_autospec

unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)

Создайте объект-макет, используя другой объект в качестве образца. Атрибуты макета будут использовать соответствующий атрибут объекта spec в качестве образца.

У функций или методов, над которыми насмехаются, будут проверяться аргументы, чтобы убедиться, что они вызываются с правильной сигнатурой.

Если spec_set имеет значение True, то при попытке установить атрибуты, не существующие в объекте spec, возникнет ошибка AttributeError.

Если в качестве спецификации используется класс, то возвращаемое значение имитатора (экземпляр класса) будет иметь ту же спецификацию. Вы можете использовать класс в качестве спецификации для объекта экземпляра, передав instance=True. Возвращаемый имитатор будет вызываемым только в том случае, если экземпляры имитатора являются вызываемыми.

create_autospec() также принимает произвольные аргументы ключевых слов, которые передаются в конструктор созданного макета.

Примеры использования автоспецификации с помощью Автоспецификация и аргумента autospec в create_autospec() смотрите в patch().

Изменено в версии 3.8: create_autospec() теперь возвращает AsyncMock, если цель является асинхронной функцией.

ЛЮБОЙ

unittest.mock.ANY

Иногда вам может понадобиться сделать утверждения о некоторых аргументах в вызове mock, но некоторые аргументы либо не важны, либо вы хотите вытащить их по отдельности из call_args и сделать более сложные утверждения для них.

Чтобы игнорировать определенные аргументы, вы можете передать объекты, которые сравниваются с всеми. Тогда вызовы assert_called_with() и assert_called_once_with() будут успешными независимо от того, что было передано.

>>> mock = Mock(return_value=None)
>>> mock('foo', bar=object())
>>> mock.assert_called_once_with('foo', bar=ANY)

ANY также может использоваться в сравнениях со списками вызовов типа mock_calls:

>>> m = MagicMock(return_value=None)
>>> m(1)
>>> m(1, 2)
>>> m(object())
>>> m.mock_calls == [call(1), call(1, 2), ANY]
True

FILTER_DIR

unittest.mock.FILTER_DIR

FILTER_DIR - это переменная уровня модуля, которая управляет тем, как объекты-макеты реагируют на dir() (только для Python 2.6 или более новых версий). По умолчанию используется True, который использует фильтрацию, описанную ниже, чтобы показывать только полезные члены. Если вам не нравится эта фильтрация или вам нужно отключить ее в диагностических целях, установите значение mock.FILTER_DIR = False.

При включенной фильтрации dir(some_mock) показывает только полезные атрибуты и включает любые динамически созданные атрибуты, которые обычно не показываются. Если макет был создан с помощью spec (или autospec, конечно), то будут показаны все атрибуты оригинала, даже если к ним еще не было доступа:

>>> dir(Mock())
['assert_any_call',
 'assert_called',
 'assert_called_once',
 'assert_called_once_with',
 'assert_called_with',
 'assert_has_calls',
 'assert_not_called',
 'attach_mock',
 ...
>>> from urllib import request
>>> dir(Mock(spec=request))
['AbstractBasicAuthHandler',
 'AbstractDigestAuthHandler',
 'AbstractHTTPHandler',
 'BaseHandler',
 ...

Многие не очень полезные (частные для Mock, а не для объекта насмешки) атрибуты с префиксом подчеркивания и двойного подчеркивания были отфильтрованы из результата вызова dir() на Mock. Если вам не нравится такое поведение, вы можете отключить его, установив переключатель уровня модуля FILTER_DIR:

>>> from unittest import mock
>>> mock.FILTER_DIR = False
>>> dir(mock.Mock())
['_NonCallableMock__get_return_value',
 '_NonCallableMock__get_side_effect',
 '_NonCallableMock__return_value_doc',
 '_NonCallableMock__set_return_value',
 '_NonCallableMock__set_side_effect',
 '__call__',
 '__class__',
 ...

В качестве альтернативы вы можете просто использовать vars(my_mock) (члены экземпляра) и dir(type(my_mock)) (члены типа), чтобы обойти фильтрацию независимо от mock.FILTER_DIR.

mock_open

unittest.mock.mock_open(mock=None, read_data=None)

Вспомогательная функция для создания макета, заменяющего использование open(). Она работает для open(), вызываемого напрямую или используемого в качестве менеджера контекста.

Аргумент mock - это объект-макет для настройки. Если None (по умолчанию), то для вас будет создан объект MagicMock, API которого ограничен методами или атрибутами, доступными для стандартных дескрипторов файлов.

read_data - это строка для возврата методами read(), readline() и readlines() дескриптора файла. Вызовы этих методов будут брать данные из read_data до тех пор, пока она не исчерпается. Имитация этих методов довольно проста: каждый раз, когда вызывается mock, read_data перематывается на начало. Если вам нужно больше контроля над данными, которые вы передаете тестируемому коду, вам придется настроить этот имитатор под себя. Если этого недостаточно, один из пакетов файловой системы in-memory на сайте PyPI может предложить реалистичную файловую систему для тестирования.

Изменено в версии 3.4: Добавлена поддержка readline() и readlines(). Макет read() изменен на потребление read_data, а не возврат его при каждом вызове.

Изменено в версии 3.5: Теперь read_data сбрасывается при каждом обращении к mock.

Изменено в версии 3.8: Добавлено __iter__() в реализацию, чтобы итерация (например, в циклах for) правильно потребляла read_data.

Использование open() в качестве менеджера контекста - отличный способ обеспечить правильное закрытие обработчиков файлов и становится все более распространенным:

with open('/some/path', 'w') as f:
    f.write('something')

Проблема в том, что даже если вы высмеиваете вызов open(), именно возвращенный объект используется в качестве менеджера контекста (и вызывает __enter__() и __exit__()).

Подсмеивание менеджеров контекста с помощью MagicMock достаточно распространено и достаточно сложно, чтобы вспомогательная функция была полезной.

>>> m = mock_open()
>>> with patch('__main__.open', m):
...     with open('foo', 'w') as h:
...         h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
 call().__enter__(),
 call().write('some stuff'),
 call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

И для чтения файлов:

>>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
...     with open('foo') as h:
...         result = h.read()
...
>>> m.assert_called_once_with('foo')
>>> assert result == 'bibble'

Автоспецификация

Автоспецификация основана на существующей функции spec в mock. Она ограничивает api имитаторов api оригинального объекта (спецификации), но является рекурсивной (реализованной лениво), так что атрибуты имитаторов имеют только те же api, что и атрибуты спецификации. Кроме того, функции/методы имитаторов имеют ту же сигнатуру вызова, что и оригинальные, поэтому при неправильном вызове они выдают ошибку TypeError.

Прежде чем я объясню, как работает автоспецификация, вот почему она необходима.

Mock - это очень мощный и гибкий объект, но он страдает двумя недостатками, когда используется для подражания объектам тестируемой системы. Один из этих недостатков специфичен для Mock api, а другой является более общей проблемой использования объектов-макетов.

Сначала проблема, специфичная для Mock. У Mock есть два метода assert, которые очень удобны: assert_called_with() и assert_called_once_with().

>>> mock = Mock(name='Thing', return_value=None)
>>> mock(1, 2, 3)
>>> mock.assert_called_once_with(1, 2, 3)
>>> mock(1, 2, 3)
>>> mock.assert_called_once_with(1, 2, 3)
Traceback (most recent call last):
 ...
AssertionError: Expected 'mock' to be called once. Called 2 times.

Поскольку макеты автоматически создают атрибуты по требованию и позволяют вызывать их с произвольными аргументами, если вы неправильно напишете один из этих методов утверждения, то ваше утверждение пропадет:

>>> mock = Mock(name='Thing', return_value=None)
>>> mock(1, 2, 3)
>>> mock.assret_called_once_with(4, 5, 6)  # Intentional typo!

Из-за опечатки ваши тесты могут пройти тихо и некорректно.

Вторая проблема более общая для мокинга. Если вы рефакторите часть вашего кода, переименуете члены и так далее, все тесты для кода, который все еще использует старый api, но использует моки вместо реальных объектов, все равно пройдут. Это означает, что все ваши тесты могут пройти, даже если ваш код сломан.

Обратите внимание, что это еще одна причина, по которой вам нужны интеграционные тесты, а также тесты модулей. Тестирование всего по отдельности - это хорошо и замечательно, но если вы не проверяете, как ваши модули «соединены вместе», остается много места для ошибок, которые тесты могли бы отловить.

В mock уже есть функция, помогающая в этом, которая называется speccing. Если вы используете класс или экземпляр в качестве spec для макета, то вы можете получить доступ к атрибутам макета только к тем, которые существуют в реальном классе:

>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with  # Intentional typo!
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'

Спецификация относится только к самому макету, поэтому у нас остается та же проблема с любыми методами на макете:

>>> mock.has_data()
<mock.Mock object at 0x...>
>>> mock.has_data.assret_called_with()  # Intentional typo!

Автоспецификация решает эту проблему. Вы можете либо передать autospec=True в patch() / patch.object(), либо использовать функцию create_autospec() для создания макета со спецификацией. Если вы используете аргумент autospec=True в patch(), то в качестве объекта спецификации будет использоваться заменяемый объект. Поскольку спецификация выполняется «лениво» (спецификация создается по мере обращения к атрибутам макета), вы можете использовать ее с очень сложными или глубоко вложенными объектами (например, модулями, которые импортируют модули, которые импортируют модули) без большого ущерба для производительности.

Вот пример его использования:

>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>

Видно, что request.Request имеет спецификацию. request.Request принимает два аргумента в конструкторе (один из которых - self). Вот что произойдет, если мы попытаемся вызвать его неправильно:

>>> req = request.Request()
Traceback (most recent call last):
 ...
TypeError: <lambda>() takes at least 2 arguments (1 given)

Спецификация также применяется к инстанцированным классам (т.е. к возвращаемому значению специальных имитаторов):

>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>

Объекты Request не являются вызываемыми, поэтому возвращаемое значение инстанцирования нашего насмешника request.Request - это не вызываемый насмешник. Благодаря этой спецификации любые опечатки в наших утверждениях будут вызывать правильную ошибку:

>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with  # Intentional typo!
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')

Во многих случаях вы просто сможете добавить autospec=True к существующим вызовам patch(), и тогда вы будете защищены от ошибок, связанных с опечатками и изменениями api.

Помимо использования autospec через patch() существует create_autospec() для создания автоспецифицированных макетов напрямую:

>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>

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

Более серьезной проблемой является то, что часто атрибуты экземпляра создаются в методе __init__() и вообще не существуют в классе. autospec не может знать о динамически созданных атрибутах и ограничивает api видимыми атрибутами.

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a
...
Traceback (most recent call last):
  ...
AttributeError: Mock object has no attribute 'a'

Существует несколько различных способов решения этой проблемы. Самый простой, но не обязательно наименее раздражающий способ - просто установить требуемые атрибуты на макете после создания. То, что autospec не позволяет вам получать атрибуты, не существующие в спецификации, не мешает вам установить их:

>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a = 33
...

Существует более агрессивная версия spec и autospec, которая действительно предотвращает установку несуществующих атрибутов. Это полезно, если вы хотите убедиться, что ваш код устанавливает только действующие атрибуты, но, очевидно, это предотвращает данный конкретный сценарий:

>>> with patch('__main__.Something', autospec=True, spec_set=True):
...   thing = Something()
...   thing.a = 33
...
Traceback (most recent call last):
 ...
AttributeError: Mock object has no attribute 'a'

Вероятно, лучшим способом решения проблемы является добавление атрибутов класса в качестве значений по умолчанию для членов экземпляра, инициализированных в __init__(). Обратите внимание, что если вы устанавливаете атрибуты по умолчанию только в __init__(), то предоставление их через атрибуты класса (разумеется, общие для экземпляров) тоже будет быстрее. Например.

class Something:
    a = 33

Это поднимает еще один вопрос. Относительно распространено предоставление значения по умолчанию None для членов, которые позже станут объектами другого типа. None будет бесполезен как спецификация, потому что он не позволит вам получить доступ к любым атрибутам или методам этого объекта. Так как None никогда не будет полезен как спецификация, и, вероятно, указывает на член, который обычно будет иметь другой тип, autospec не использует спецификацию для членов, которые установлены в None. Это будут обычные макеты (хорошо - MagicMocks):

>>> class Something:
...     member = None
...
>>> mock = create_autospec(Something)
>>> mock.member.foo.bar.baz()
<MagicMock name='mock.member.foo.bar.baz()' id='...'>

Если модификация производственных классов для добавления значений по умолчанию вам не по душе, то есть и другие варианты. Один из них - просто использовать экземпляр в качестве спецификации, а не класс. Другой вариант - создать подкласс производственного класса и добавить параметры по умолчанию в подкласс, не затрагивая производственный класс. Оба варианта требуют использования альтернативного объекта в качестве спецификации. К счастью, patch() поддерживает это - вы можете просто передать альтернативный объект в качестве аргумента autospec:

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> class SomethingForTest(Something):
...   a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>
4

Это относится только к классам или уже инстанцированным объектам. Вызов насмешливого класса для создания насмешливого экземпляра не создает реального экземпляра. Это только поиск атрибутов - вместе с вызовом dir() - который выполняется.

Пломбирование макетов

unittest.mock.seal(mock)

Seal отключит автоматическое создание макетов при рекурсивном доступе к атрибуту запечатываемого макета или к любому из его атрибутов, которые уже являются макетами.

Если имитируемому объекту с именем или спецификацией присвоен атрибут, он не будет учитываться в цепочке уплотнения. Это позволяет предотвратить фиксацию части объекта-макета с помощью печати.

>>> mock = Mock()
>>> mock.submock.attribute1 = 2
>>> mock.not_submock = mock.Mock(name="sample_name")
>>> seal(mock)
>>> mock.new_attribute  # This will raise AttributeError.
>>> mock.submock.attribute2  # This will raise AttributeError.
>>> mock.not_submock.attribute2  # This won't raise.

Добавлено в версии 3.7.

Вернуться на верх