Spec-Zone.ru › Python 3.6

unittest.mock — библиотека создания эмуляторов объектов

Новая в версии 3.3.

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

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

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

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

Mock очень прост в использовании и предназначен для использования с unittest. Mock основан на паттерне «действие → утверждение», а не на паттерне «запись → воспроизведение», используемом во многих фреймворках для эмуляции.

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

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

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()

Примечание

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

С 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 магических методов. Самый простой способ использования магических методов — с классом 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, что и объекты, которые они заменяют, вы можете использовать авто-спецификацию. Авто-спецификацию можно сделать через аргумент autospec к patch или функцию 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__.

26.5.2. Класс 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: Это может быть либо список строк, либо существующий объект (класс или экземпляр), который служит спецификацией для объекта mock. Если вы передаёте объект, то список строк формируется путём вызова dir на объекте (исключая неподдерживаемые магические атрибуты и методы). Обращение к любому атрибуту, отсутствующему в этом списке, вызовет AttributeError.

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

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

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

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

    side_effect можно очистить, установив его в None.

  • return_value: Значение, возвращаемое при вызове мока. По умолчанию это новый Mock (созданный при первом доступе). См. атрибут return_value.
  • unsafe: По умолчанию, если какой-либо атрибут начинается с assert или assret, будет поднято AttributeError. Передача unsafe=True позволит получить доступ к этим атрибутам.

    Новое в версии 3.5.

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

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

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

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

assert_called(*args, **kwargs)

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

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

Новое в версии 3.6.

assert_called_once(*args, **kwargs)

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

>>> 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 ложно (по умолчанию), то вызовы должны быть последовательными. До или после указанных вызовов могут быть дополнительные вызовы.

Если any_order истинно, то вызовы могут быть в любом порядке, но они все должны присутствовать в 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 или какие-либо дочерние атрибуты, которые вы установили с помощью обычного присваивания по умолчанию. Если вы хотите сбросить return_value или side_effect, то передайте соответствующий параметр в виде True. Дочерние моки и мок возвращаемого значения (если таковой имеется) также сбрасываются.

Примечание

return_value и side_effect являются именованными аргументами.

mock_add_spec(spec, spec_set=False)

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

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

attach_mock(mock, attribute)

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

configure_mock(**kwargs)

Установить атрибуты мока через именованные аргументы.

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

>>> 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(return_value=None)
>>> mock.called
False
>>> mock()
>>> mock.called
True
call_count

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

>>> 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.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).

Если вы передадите итерируемый объект, он будет использован для получения итератора, который должен возвращать значение при каждом вызове. Это значение может быть либо экземпляром исключения, которое должно быть поднято, либо значением, которое должно быть возвращено при вызове мока (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 можно установить в конструкторе. Вот пример, который добавляет единицу к значению, с которым вызывается мок, и возвращает его:

>>> 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 (если мок не вызывался), либо аргументы, с которыми мок был вызван последний раз. Это будет в форме кортежа: первый член — упорядоченные аргументы, с которыми был вызван мок (или пустой кортеж), а второй член — произвольные именованные аргументы (или пустой словарь).

>>> 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(3, 4, 5, key='fish', next='w00t!')
>>> mock.call_args
call(3, 4, 5, key='fish', next='w00t!')

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

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. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.

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. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.

mock_calls

mock_calls записывает все вызовы объекта-мока, его методов, магических методов и моков возвращаемых значений.

>>> 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. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.

Примечание

Способ записи 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, которые не имеют значения для невызываемого мока.

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

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

Классы 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

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

>>> 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: Добавлена интроспекция сигнатуры для специфицированных и автоспецифицированных объектов-моков.

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()

26.5.2.1. Вызов

Объекты-заглушки вызываемы. Вызов вернет значение, заданное как атрибут return_value. Значение по умолчанию — новый объект-заглушка; он создаётся в первый раз при обращении к возвращаемому значению (явно или при вызове объекта-заглушки) — но он сохраняется и возвращается каждый раз.

Вызовы объекта будут записаны в атрибутах, таких как 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 также может быть любым итерируемым объектом. Повторные вызовы заглушки будут возвращать значения из итерируемого объекта (пока итерируемый объект не исчерпан и не будет вызвано исключение StopIteration):

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

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

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

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

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

Вам может понадобиться, чтобы объект-заглушка возвращал 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

26.5.2.3. Имена заглушек и атрибут name

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

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

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

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

26.5.2.4. Прикрепление заглушек в качестве атрибутов

Когда вы прикрепляете заглушку в качестве атрибута другой заглушки (или в качестве возвращаемого значения), она становится «дочерней» заглушкой. Вызовы дочерней заглушки записываются в атрибутах 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

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

26.5.3. Декораторы-замены

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

26.5.3.1. patch

Примечание

patch() прост в использовании. Ключ — произвести замену в нужном пространстве имён. См. раздел где производить замену.

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

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

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

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

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

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

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

Более мощная форма spec — autospec. Если вы установите autospec=True, то mock будет создан со спецификацией из заменяемого объекта. Все атрибуты mock также будут иметь спецификацию соответствующего атрибута замещаемого объекта. Методы и функции, которые имитируются, будут проверять свои аргументы и будут генерировать TypeError, если они вызываются с неправильной сигнатурой. Для mocks, заменяющих класс, их возвращаемое значение («экземпляр») будет иметь ту же спецификацию, что и класс. См. функцию 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() создаёт для вас объект mock.

patch() принимает произвольные ключевые аргументы. Они будут переданы в Mock (или new_callable) при создании.

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

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

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

Замена класса подменяет класс на MagicMock экземпляр. Если класс создаётся в тестируемом коде, то он будет return_value mock, который будет использоваться.

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

Для настройки значений возврата методов экземпляров в заменённом классе необходимо это сделать в 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 будет иметь ту же спецификацию.

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

Аргумент new_callable полезен, когда вы хотите использовать альтернативный класс по умолчанию MagicMock для созданного mock. Например, если вы хотели, чтобы использовался 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() создаёт для вас mock, обычно первое, что вам нужно сделать, это настроить mock. Часть этой настройки можно выполнить в вызове patch. Любые произвольные ключевые аргументы, которые вы передаёте в вызов, будут использованы для установки атрибутов на созданный mock:

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

Помимо атрибутов созданного mock, таких как return_value и side_effect, атрибуты дочерних mock также могут быть настроены. Они не являются синтаксически корректными для передачи в качестве ключевых аргументов напрямую, но словарь с этими ключами всё ещё может быть расширен в вызов 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

26.5.3.2. patch.object

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

Заменяет указанный член (attribute) объекта (target) на mock-объект.

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

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

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

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

>>> @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().

26.5.3.3. patch.dict

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

Заменить словарь или подобный объекту словарь и восстановить словарь до первоначального состояния после завершения теста.

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

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

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

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

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

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

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

>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert 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']

26.5.3.4. 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
...

26.5.3.5. Методы замещения: start и stop

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

Чтобы их использовать, вызовите 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(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(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.

26.5.3.6. Замена встроенных функций

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

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

26.5.3.7. 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

26.5.3.8. Вложение декораторов замещения

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

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

>>> @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. Порядок созданных подстановок, передаваемых в вашу тестовую функцию, соответствует этому порядку.

26.5.3.9. Где выполнять замену

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

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

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

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 и кажется, что наша замена не сработала.

END_OF_DOCUMENT_MARKER

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

@patch('b.SomeClass')

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

@patch('a.SomeClass')

26.5.3.10. Замена дескрипторов и прокси-объектов

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

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

26.5.4.1. Моделирование магических методов

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__
  • __floor__, __trunc__ и __ceil__
  • Сравнения: __lt__, __gt__, __le__, __ge__, __eq__ и __ne__
  • Методы контейнеров: __getitem__, __setitem__, __delitem__, __contains__, __len__, __iter__, __reversed__ и __missing__
  • Менеджер контекста: __enter__ и __exit__
  • Унарные числовые методы: __neg__, __pos__ и __invert__
  • Числовые методы (включая правые и операторы присваивания): __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__

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

  • __getattr__, __setattr__, __init__ и __new__
  • __prepare__, __instancecheck__, __subclasscheck__, __del__

26.5.4.2. Magic Mock

Существует два 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, которые не имеют смысла для невызываемого мока.

Магические методы настраиваются с помощью объектов 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
  • __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 экземпляр остается изолированным от других.

26.5.5. Вспомогательные функции

26.5.5.1. sentinel

unittest.mock.sentinel

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

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

Атрибуты sentinel не сохраняют свою идентичность при copied или pickled.

Иногда при тестировании необходимо проверить, что определённый объект передаётся в качестве аргумента другому методу или возвращается. Часто для этого создают именованные объекты-сентинэли. 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
>>> sentinel.some_object
sentinel.some_object

26.5.5.2. DEFAULT

unittest.mock.DEFAULT

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

26.5.5.3. call

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 — это кортеж (позиционные аргументы, ключевые аргументы) или (имя, позиционные аргументы, ключевые аргументы) в зависимости от способа его построения. Когда вы строите их сами, это не особенно интересно, но объекты call в атрибутах Mock.call_args, Mock.call_args_list и Mock.mock_calls могут быть проанализированы для получения отдельных аргументов, которые они содержат.

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

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

>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3, arg='one', arg2='two')
>>> kall = m.call_args
>>> args, kwargs = kall
>>> args
(1, 2, 3)
>>> kwargs
{'arg2': 'two', 'arg': 'one'}
>>> args is kall[0]
True
>>> 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
{'arg2': 'three', 'arg': 'two'}
>>> name is m.mock_calls[0][0]
True

26.5.5.4. create_autospec

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

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

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

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

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

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

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

26.5.5.5. ANY

unittest.mock.ANY

Иногда вам может потребоваться сделать утверждения о некоторых аргументах в вызове заглушки, но либо не обращать внимания на некоторые аргументы, либо извлечь их индивидуально из 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

26.5.5.6. 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_once_with',
 'assert_called_with',
 'assert_has_calls',
 '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.

26.5.5.7. 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.

Использование 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'

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

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

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

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

Во-первых, проблема, специфичная для 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.

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

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

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

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

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

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

>>> from urllib import request
>>> mock = Mock(spec=request.Request)
>>> mock.assret_called_with
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()

Автоспецификация решает эту проблему. Вы можете передать 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
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 должен выполнить интроспекцию (обратиться к атрибутам) спецификации. По мере прохождения по атрибутам мока происходит соответствующее прохождение по исходному объекту под капотом. Если какие-либо ваши специфицированные объекты имеют свойства или описатели, которые могут инициировать выполнение кода, вы можете не иметь возможности использовать автоспецификацию. С другой стороны, лучше спроектировать ваши объекты таким образом, чтобы интроспекция была безопасной 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() — которые выполняются.

© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.6/library/unittest.mock.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API