unittest.mock — библиотека объектов-заглушек
Добавлено в версии 3.3.
Исходный код: Lib/unittest/mock.py
unittest.mock — это библиотека для тестирования в Python. Она позволяет заменять части тестируемой системы объектами-заглушками и проверять, как они использовались.
unittest.mock предоставляет базовый класс Mock, избавляя от необходимости создавать множество заглушек в наборе тестов. Выполнив действие, можно проверить, какие методы и атрибуты использовались и с какими аргументами их вызывали. Также можно задать возвращаемые значения и необходимые атрибуты обычным способом.
Кроме того, mock предоставляет декоратор patch(), который заменяет атрибуты на уровне модуля и класса на время выполнения теста, а также sentinel для создания уникальных объектов. Примеры использования Mock, MagicMock и patch() см. в кратком руководстве.
Mock предназначен для использования с unittest и основан на шаблоне «действие → проверка», а не на используемом во многих библиотеках имитации шаблоне «запись → воспроизведение».
Для более ранних версий Python доступен обратный перенос unittest.mock — пакет mock на 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 позволяет выполнять побочные эффекты, в том числе вызывать исключение при обращении к заглушке:
>>> from unittest.mock import Mock
>>> 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: missing a required argument: 'b'
create_autospec() также можно применять к классам: в этом случае копируется сигнатура метода __init__. Для вызываемых объектов копируется сигнатура метода __call__.
Класс Mock
Mock — гибкий объект-мок, предназначенный для замены заглушек и тестовых дублёров в вашем коде. Моки можно вызывать; при обращении к их атрибутам они создают новые моки [1]. При повторном обращении к одному и тому же атрибуту всегда возвращается один и тот же мок. Моки записывают, как вы их используете, что позволяет проверять, что ваш код с ними сделал.
MagicMock — подкласс Mock, у которого заранее созданы все магические методы, готовые к использованию. Есть также варианты, которые нельзя вызывать; они полезны при подмене объектов, не являющихся вызываемыми: NonCallableMock и NonCallableMagicMock
Декораторы patch() позволяют легко временно заменить классы в определённом модуле объектом Mock. По умолчанию patch() создаст для вас объект MagicMock. С помощью аргумента new_callable можно указать альтернативный класс Mock для 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__возвращает класс объекта-спецификации. Это позволяет мокам проходить проверкиisinstance(). -
spec_set: более строгий вариант spec. Если он используется, попытка задать или получить атрибут мока, которого нет у объекта, переданного как spec_set, вызовет исключение
AttributeError. -
side_effect: функция, вызываемая каждый раз при вызове мока. См. атрибут
side_effect. Полезен для вызова исключений или динамического изменения возвращаемых значений. Функция вызывается с теми же аргументами, что и мок; если она возвращает неDEFAULT, возвращаемое ею значение используется как результат вызова.В качестве side_effect также можно задать класс или экземпляр исключения. В этом случае при вызове мока будет вызвано исключение.
Если side_effect — итерируемый объект, каждый вызов мока будет возвращать следующее значение из этого объекта.
Чтобы очистить side_effect, задайте ему значение
None. -
return_value: значение, возвращаемое при вызове мока. По умолчанию это новый Mock (создаётся при первом обращении). См. атрибут
return_value. -
unsafe: по умолчанию обращение к любому атрибуту, имя которого начинается с assert, assret, asert, aseert или assrt, вызывает исключение
AttributeError. Передачаunsafe=Trueразрешит обращение к этим атрибутам.Добавлено в версии 3.5.
-
wraps: объект, который будет обёрнут объектом-моком. Если wraps не равно
None, вызов Mock будет передан обёрнутому объекту (и возвращён его реальный результат). Обращение к атрибуту мока вернёт объект Mock, оборачивающий соответствующий атрибут обёрнутого объекта (поэтому попытка обратиться к несуществующему атрибуту вызовет исключениеAttributeError).Если для мока явно задан return_value, вызовы не передаются обёрнутому объекту, а вместо этого возвращается значение return_value.
- name: если у мока есть имя, оно будет использоваться в его repr. Это может быть полезно для отладки. Имя передаётся дочерним мокам.
Мокам также можно передавать произвольные именованные аргументы. Они будут использоваться для установки атрибутов мока после его создания. Подробнее см. метод
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. Calls: [call(), call()].
Добавлено в версии 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. Calls: [call('foo', bar='baz'), call('other', bar='values')].
-
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. Calls: [call()].
Добавлено в версии 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Это может быть полезно, если вы хотите выполнить серию проверок, используя один и тот же объект повторно.
Если параметр return_value имеет значение
True, он сбрасываетreturn_value:>>> mock = Mock(return_value=5) >>> mock('hello') 5 >>> mock.reset_mock(return_value=True) >>> mock('hello') <Mock name='mock()' id='...'>Если параметр side_effect имеет значение
True, он сбрасываетside_effect:>>> mock = Mock(side_effect=ValueError) >>> mock('hello') Traceback (most recent call last): ... ValueError >>> mock.reset_mock(side_effect=True) >>> mock('hello') <Mock name='mock()' id='...'>Обратите внимание: по умолчанию
reset_mock()не очищаетreturn_value,side_effectили дочерние атрибуты, заданные обычным присваиванием.Дочерние моки также сбрасываются.
Изменено в версии 3.6: В функцию reset_mock добавлены два аргумента, доступных только по имени.
-
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(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
Присваивание
Noneатрибутуside_effectочищает его:>>> 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, — это позиционные аргументы, переданные моку (или пустой кортеж), а второй элемент, доступный также через свойство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. Это кортежи, поэтому их можно распаковать, чтобы получить отдельные аргументы и выполнить более сложные проверки. См. раздел вызовы как кортежи.Изменено в версии 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. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. раздел вызовы как кортежи.
-
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
Вызываемый мок, созданный с 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: Добавлен анализ сигнатур для моков со спецификацией и автоматически заданной спецификацией.
-
class unittest.mock.PropertyMock(*args, **kwargs) -
Мок, предназначенный для использования в качестве
propertyили другого дескриптора класса.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()
Предупреждение
Если PropertyMock вызывает исключение AttributeError, оно будет интерпретировано как отсутствие дескриптора, и для родительского мока будет вызван __getattr__():
>>> m = MagicMock() >>> no_attribute = PropertyMock(side_effect=AttributeError) >>> type(m).my_property = no_attribute >>> m.my_property <MagicMock name='mock.my_property' id='140165240345424'>
Подробности см. в разделе __getattr__().
-
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() >>> inspect.iscoroutinefunction(mock) True >>> inspect.isawaitable(mock()) True
Результатом
mock()является асинхронная функция, результатом выполнения которой после ожидания будетside_effectилиreturn_value:- если
side_effect— функция, асинхронная функция вернёт результат этой функции, - если
side_effect— исключение, асинхронная функция вызовет это исключение, - если
side_effect— итерируемый объект, асинхронная функция вернёт следующее значение из итерируемого объекта, однако, если последовательность результатов исчерпана, немедленно вызываетсяStopAsyncIteration, - если
side_effectне определён, асинхронная функция вернёт значение, заданноеreturn_value, поэтому по умолчанию асинхронная функция возвращает новый объектAsyncMock.
Если задать spec для
MockилиMagicMockкак асинхронную функцию, после вызова будет возвращён объект корутины.>>> 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() -
Проверяет, что ожидание mock-объекта выполнялось хотя бы один раз. Обратите внимание: это не то же самое, что вызов объекта; необходимо использовать ключевое слово
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-объекта выполнялось ровно один раз.
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.assert_awaited_once() >>> asyncio.run(main()) >>> mock.assert_awaited_once() Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited once. Awaited 2 times.
-
assert_awaited_with(*args, **kwargs) -
Проверяет, что последнее ожидание выполнялось с указанными аргументами.
>>> 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 await not found. Expected: mock('other') Actual: mock('foo', bar='bar')
-
assert_awaited_once_with(*args, **kwargs) -
Проверяет, что ожидание mock-объекта выполнялось ровно один раз и с указанными аргументами.
>>> 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-объект с указанными аргументами.
>>> 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) -
Проверяет, что mock-объект ожидался с указанными вызовами. Для проверки ожиданий используется список
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-объекта ни разу не выполнялось.
>>> 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-объект ещё не ожидался), либо аргументам, с которыми mock-объект ожидался в последний раз. Работает так же, как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-объекта в порядке их выполнения (длина списка равна количеству ожиданий). До выполнения первого ожидания список пуст.
>>> 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')]
- если
-
class unittest.mock.ThreadingMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, *, timeout=UNSET, **kwargs) -
Версия
MagicMockдля тестов многопоточности. ОбъектThreadingMockпредоставляет дополнительные методы ожидания вызова, а не немедленной проверки его наличия.Время ожидания по умолчанию задаётся аргументом
timeoutили, если он не указан, атрибутомThreadingMock.DEFAULT_TIMEOUT; по умолчанию ожидание блокирующее (None).Глобальное время ожидания по умолчанию можно настроить, задав значение
ThreadingMock.DEFAULT_TIMEOUT.-
wait_until_called(*, timeout=UNSET) -
Ожидает вызова mock-объекта.
Если при создании mock-объекта было задано время ожидания или аргумент времени ожидания передан этой функции, функция вызывает
AssertionError, если вызов не произошёл вовремя.>>> mock = ThreadingMock() >>> thread = threading.Thread(target=mock) >>> thread.start() >>> mock.wait_until_called(timeout=1) >>> thread.join()
-
wait_until_any_call_with(*args, **kwargs) -
Ожидает вызова mock-объекта с указанными аргументами.
Если при создании mock-объекта было задано время ожидания, функция вызывает
AssertionError, если вызов не произошёл вовремя.>>> mock = ThreadingMock() >>> thread = threading.Thread(target=mock, args=("arg1", "arg2",), kwargs={"arg": "thing"}) >>> thread.start() >>> mock.wait_until_any_call_with("arg1", "arg2", arg="thing") >>> thread.join()
-
DEFAULT_TIMEOUT -
Глобальное время ожидания по умолчанию в секундах для создания экземпляров
ThreadingMock.
Добавлено в версии 3.13.
-
Вызов
Объекты Mock являются вызываемыми. Вызов возвращает значение, заданное атрибутом return_value. Значением по умолчанию является новый объект Mock; он создаётся при первом обращении к значению возврата (явном или посредством вызова Mock), сохраняется и возвращается при каждом последующем обращении.
Вызовы объекта записываются в такие атрибуты, как call_args и call_args_list.
Если задан side_effect, он вызывается после регистрации вызова, поэтому даже если side_effect вызывает исключение, вызов всё равно будет зарегистрирован.
Проще всего заставить mock-объект вызывать исключение при вызове — задать для 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 является функцией, вызовы mock-объекта возвращают значение, возвращаемое этой функцией. Функция side_effect вызывается с теми же аргументами, что и mock-объект. Это позволяет динамически изменять возвращаемое значение в зависимости от входных данных:
>>> 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-объект продолжал возвращать значение по умолчанию (новый 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 = (33, ValueError, 66) >>> m = MagicMock(side_effect=iterable) >>> m() 33 >>> m() Traceback (most recent call last): ... ValueError >>> m() 66
Удаление атрибутов
Объекты Mock создают атрибуты по требованию. Это позволяет им имитировать объекты любого типа.
Возможно, вам нужно, чтобы mock-объект возвращал False при вызове hasattr() или вызывал AttributeError при получении атрибута. Это можно сделать, передав объект в качестве spec для mock-объекта, но это не всегда удобно.
Атрибуты можно «заблокировать», удалив их. После удаления обращение к атрибуту вызовет 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
Имена mock-объектов и атрибут name
Поскольку «name» — аргумент конструктора Mock, задать mock-объекту атрибут «name» при создании, просто передав его, нельзя. Есть два способа это сделать. Один из вариантов — использовать configure_mock():
>>> mock = MagicMock() >>> mock.configure_mock(name='my_name') >>> mock.name 'my_name'
Более простой вариант — просто задать атрибут «name» после создания mock-объекта:
>>> mock = MagicMock() >>> mock.name = "foo"
Присоединение mock-объектов в качестве атрибутов
Когда mock-объект присоединяется в качестве атрибута другого mock-объекта (или в качестве значения возврата), он становится его «дочерним» объектом. Вызовы дочернего объекта записываются в атрибуты родительского объекта method_calls и mock_calls. Это удобно для настройки дочерних mock-объектов с последующим присоединением к родительскому или для присоединения mock-объектов к родительскому, который записывает все вызовы дочерних объектов и позволяет проверять порядок вызовов между mock-объектами:
>>> 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-объект с именем. Это позволяет предотвратить его «присоединение к родителю», если по какой-либо причине оно нежелательно.
>>> 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 []
Mock-объекты, созданные для вас с помощью patch(), автоматически получают имена. Чтобы присоединить к родительскому mock-объекту именованные mock-объекты, используйте метод 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')]
Патчеры
Декораторы patch используются для подмены объектов только в пределах области видимости декорируемой ими функции. Они автоматически восстанавливают исходные объекты, даже если возникает исключение. Все эти функции также можно использовать в операторах with или в качестве декораторов классов.
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 не указан, цель заменяется на
AsyncMock, если подменяемый объект является асинхронной функцией, или наMagicMockв остальных случаях. Еслиpatch()используется как декоратор и new не указан, созданная имитация передаётся декорируемой функции в качестве дополнительного аргумента. Если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 позволяет указать другой класс или вызываемый объект, который будет вызван для создания объекта new. По умолчанию для асинхронных функций используется
AsyncMock, а для остальных —MagicMock.Более мощная форма spec — это autospec. Если задать
autospec=True, имитация будет создана с использованием спецификации заменяемого объекта. Все атрибуты имитации также будут иметь спецификации соответствующих атрибутов заменяемого объекта. Для имитируемых методов и функций будут проверяться аргументы, и при вызове с неправильной сигнатурой будет вызвано исключениеTypeError. Для имитаций, заменяющих класс, их возвращаемое значение («экземпляр») будет иметь ту же спецификацию, что и класс. См. функциюcreate_autospec()и раздел Autospeccing.Вместо
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, чтобы каждый раз возвращать новую имитацию. Можно также задать для 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() заменяет класс, возвращаемое значение созданной имитации будет иметь ту же спецификацию.
>>> 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() создаёт имитацию, обычно первым делом её нужно настроить. Часть настроек можно задать при вызове 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()
patch.object
-
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
...
Методы patch: 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(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
Чтобы выполнить несколько подмен, можно просто расположить декораторы друг за другом.
Несколько декораторов patch можно расположить друг за другом следующим образом:
>>> @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() работает, временно заменяя объект, на который ссылается имя, другим объектом. На один и тот же объект может ссылаться множество имён, поэтому для корректной подмены необходимо заменить имя, которое использует тестируемая система.
Основной принцип: подменять объект нужно там, где он ищется, а это не обязательно место, где он определён. Несколько примеров помогут это пояснить.
Представим, что у нас есть проект, который нужно протестировать, со следующей структурой:
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.
MagicMock и поддержка магических методов
Имитация магических методов
Mock поддерживает имитацию методов протокола Python, также известных как «магические методы». Это позволяет mock-объектам заменять контейнеры или другие объекты, реализующие протоколы Python.
Поскольку магические методы ищутся иначе, чем обычные методы [2], эта поддержка реализована особым образом. Это означает, что поддерживаются только определённые магические методы. Поддерживаемый список включает почти все из них. Если вам не хватает каких-либо методов, сообщите нам.
Чтобы имитировать магические методы, присвойте интересующий вас метод функции или экземпляру mock-объекта. Если вы используете функцию, она должна принимать 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.
Примечание
Если при создании mock-объекта вы используете ключевой аргумент 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__ - Числовые методы (включая варианты для правого операнда и варианты с изменением на месте):
__add__,__sub__,__mul__,__matmul__,__truediv__,__floordiv__,__mod__,__divmod__,__lshift__,__rshift__,__and__,__xor__,__or__и__pow__ - Методы преобразования чисел:
__complex__,__int__,__float__и__index__ - Методы дескрипторов:
__get__,__set__и__delete__ - Сериализация с помощью pickle:
__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: 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__: хеш по умолчанию для mock-объекта -
__str__: строковое представление mock-объекта по умолчанию -
__sizeof__: размер mock-объекта по умолчанию
Например:
>>> 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
Возвращаемое значение __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__
Магические методы следует искать в классе, а не в экземпляре. В разных версиях Python это правило применяется непоследовательно. Поддерживаемые методы протокола должны работать во всех поддерживаемых версиях Python.
Функция фактически привязывается к классу, но каждый экземпляр Mock изолирован от остальных.
Вспомогательные средства
sentinel
-
unittest.mock.sentinel -
Объект
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, чтобы указать на необходимость использовать обычное возвращаемое значение.
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 объекта mock появляется несколько записей. Вручную составлять последовательность вызовов может быть утомительно.
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
>>> 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) -
Создаёт объект mock, используя другой объект в качестве спецификации. Атрибуты mock будут использовать соответствующие атрибуты объекта спецификации в качестве спецификации.
Для имитируемых функций и методов проверяются аргументы, чтобы убедиться, что вызов выполнен с правильной сигнатурой.
Если spec_set имеет значение
True, попытка задать атрибуты, отсутствующие в объекте спецификации, вызовет исключениеAttributeError.Если в качестве спецификации используется класс, возвращаемое значение mock (экземпляр класса) будет иметь ту же спецификацию. Чтобы использовать класс в качестве спецификации для объекта-экземпляра, передайте
instance=True. Возвращённый объект mock будет вызываемым, только если вызываемы экземпляры mock.create_autospec()также принимает произвольные именованные аргументы, которые передаются конструктору созданного объекта mock.
Примеры использования автоматического создания спецификаций с помощью create_autospec() и аргумента autospec для patch() см. в разделе Автоматическое создание спецификаций.
Изменено в версии 3.8: create_autospec() теперь возвращает AsyncMock, если целевой объект является асинхронной функцией.
ANY
-
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
ANY не ограничен сравнением с объектами вызовов, поэтому его также можно использовать в проверках тестов:
class TestStringMethods(unittest.TestCase):
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', ANY])
FILTER_DIR
-
unittest.mock.FILTER_DIR
FILTER_DIR — это переменная уровня модуля, которая управляет поведением объектов mock при вызове dir(). По умолчанию установлено значение True, при котором применяется описанная ниже фильтрация и отображаются только полезные элементы. Если эта фильтрация вам не нравится или её нужно отключить для диагностики, задайте mock.FILTER_DIR = False.
При включённой фильтрации dir(some_mock) показывает только полезные атрибуты, включая динамически созданные атрибуты, которые обычно не отображаются. Если объект mock создан с спецификацией (или, конечно, с 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', ...
При вызове dir() для Mock результат не включает многие мало полезные атрибуты с одним или двумя символами подчёркивания в начале (они относятся к 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)) (элементы типа), чтобы обойти фильтрацию независимо от значения FILTER_DIR.
mock_open
-
unittest.mock.mock_open(mock=None, read_data='') -
Вспомогательная функция для создания mock, заменяющего использование
open(). Она работает, еслиopen()вызывается напрямую или используется как менеджер контекста.Аргумент mock — это объект mock, который нужно настроить. Если передано
None(значение по умолчанию), для вас будет созданMagicMock, API которого ограничен методами и атрибутами, доступными у стандартных файловых дескрипторов.read_data — это строка, которую должны возвращать методы файлового дескриптора
read(),readline()иreadlines(). Эти методы возвращают данные из read_data, пока они не закончатся. Имитирующая их работа довольно проста: при каждом вызове mock указатель read_data возвращается в начало. Если требуется больше контроля над данными, передаваемыми проверяемому коду, настройте этот mock самостоятельно. Если этого недостаточно, один из пакетов файловой системы в памяти на PyPI может предоставить реалистичную файловую систему для тестирования.Изменено в версии 3.4: Добавлена поддержка
readline()иreadlines(). Поведение mock для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 объектов mock API исходного объекта (спецификации), но делает это рекурсивно (лениво), поэтому атрибуты объектов mock имеют только тот же API, что и соответствующие атрибуты спецификации. Кроме того, имитируемые функции и методы имеют ту же сигнатуру вызова, что и оригинал, поэтому при неправильном вызове они вызывают исключение TypeError.
Прежде чем объяснять, как работает автоматическое создание спецификаций, расскажу, зачем оно нужно.
Mock — очень мощный и гибкий объект, но у него есть недостаток, характерный для имитации объектов. Если вы измените структуру кода, переименуете элементы и так далее, все тесты кода, который продолжает использовать старый API вместо реальных объектов использует mock, всё равно пройдут. Это значит, что все тесты могут пройти, даже если код неисправен.
Изменено в версии 3.5: До версии 3.5 тесты с опечаткой в слове assert не выдавали ошибку, хотя должны были. Это поведение по-прежнему можно получить, передав unsafe=True в Mock.
Заметьте, что это ещё одна причина, по которой помимо модульных тестов нужны интеграционные. Тестировать всё по отдельности — это хорошо, но если не проверять, как ваши компоненты «соединены друг с другом», остаётся много возможностей для ошибок, которые могли бы выявить тесты.
В unittest.mock уже есть функция, помогающая решить эту проблему, — создание спецификаций. Если использовать класс или экземпляр в качестве spec для mock, можно обращаться только к тем атрибутам mock, которые существуют у реального класса:
>>> 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, поэтому проблема с его методами остаётся:
>>> mock.header_items() <mock.Mock object at 0x...> >>> mock.header_items.assret_called_with() # Intentional typo!
Эту проблему решает автоматическое создание спецификаций. Можно передать autospec=True в patch() / patch.object() или использовать функцию create_autospec(), чтобы создать mock со спецификацией. Если передать аргумент autospec=True в patch(), заменяемый объект будет использоваться как объект спецификации. Поскольку спецификация создаётся «лениво» (по мере обращения к атрибутам mock), этот механизм можно использовать с очень сложными или глубоко вложенными объектами (например, модулями, импортирующими модули, которые импортируют другие модули) без существенного снижения производительности.
Вот пример использования:
>>> 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)
Спецификация применяется и к созданным экземплярам классов (то есть к возвращаемым значениям mock со спецификацией):
>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Объекты Request не являются вызываемыми, поэтому результат создания экземпляра имитированного request.Request — это невызываемый объект mock. При наличии спецификации любая опечатка в проверках вызовет соответствующую ошибку:
>>> 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() для непосредственного создания mock с автоматической спецификацией:
>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
Однако у этого подхода есть оговорки и ограничения, поэтому он не используется по умолчанию. Чтобы узнать, какие атрибуты доступны у объекта спецификации, autospec должен изучить спецификацию (обратиться к её атрибутам). При обходе атрибутов mock в фоновом режиме происходит соответствующий обход исходного объекта. Если у объектов со спецификацией есть свойства или дескрипторы, которые могут запускать код, использовать 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'
Есть несколько способов решить эту проблему. Самый простой, но не обязательно самый удобный, — задать нужные атрибуты объекту mock после его создания. То, что 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. Для них создаются обычные объекты mock (точнее, MagicMock):
>>> 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='...'>
Это относится только к классам или уже созданным объектам. Вызов имитируемого класса для создания экземпляра mock не создаёт реальный экземпляр. Выполняется только поиск атрибутов и вызовы dir().
Фиксация объектов mock
-
unittest.mock.seal(mock) -
Функция seal отключает автоматическое создание объектов mock при обращении к атрибуту фиксируемого объекта mock или к любому из его атрибутов, которые уже являются объектами mock, рекурсивно.
Если атрибуту присвоен экземпляр mock с именем или спецификацией, он не будет включён в цепочку фиксации. Это позволяет исключить часть объекта mock из фиксации.
>>> 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.
Порядок приоритета side_effect, return_value и wraps
Порядок приоритета следующий:
side_effectreturn_value- wraps
Если заданы все три значения, mock вернёт значение из side_effect, полностью игнорируя return_value и обёрнутый объект. Если заданы любые два из них, значение вернёт тот, у которого выше приоритет. Независимо от того, какое значение было задано первым, порядок приоритета остаётся неизменным.
>>> from unittest.mock import Mock >>> class Order: ... @staticmethod ... def get_value(): ... return "third" ... >>> order_mock = Mock(spec=Order, wraps=Order) >>> order_mock.get_value.side_effect = ["first"] >>> order_mock.get_value.return_value = "second" >>> order_mock.get_value() 'first'
Поскольку None является значением по умолчанию для side_effect, если присвоить ему это значение снова — None, — приоритет будет определяться между return_value и обёрнутым объектом, а side_effect будет проигнорирован.
>>> order_mock.get_value.side_effect = None >>> order_mock.get_value() 'second'
Если значение, возвращаемое side_effect, равно DEFAULT, оно игнорируется, а для получения возвращаемого значения используется следующий элемент в порядке приоритета.
>>> from unittest.mock import DEFAULT >>> order_mock.get_value.side_effect = [DEFAULT] >>> order_mock.get_value() 'second'
Если Mock оборачивает объект, значением по умолчанию для return_value будет DEFAULT.
>>> order_mock = Mock(spec=Order, wraps=Order) >>> order_mock.return_value sentinel.DEFAULT >>> order_mock.get_value.return_value sentinel.DEFAULT
Порядок приоритета игнорирует это значение и переходит к последнему элементу — обёрнутому объекту.
Поскольку реальный вызов выполняется для обёрнутого объекта, создание экземпляра такого mock вернёт настоящий экземпляр класса. Необходимо передать позиционные аргументы, если обёрнутый объект их требует.
>>> order_mock_instance = order_mock() >>> isinstance(order_mock_instance, Order) True >>> order_mock_instance.get_value() 'third'
>>> order_mock.get_value.return_value = DEFAULT >>> order_mock.get_value() 'third'
>>> order_mock.get_value.return_value = "second" >>> order_mock.get_value() 'second'
Но если присвоить ему None, это значение не будет проигнорировано, поскольку оно присвоено явно. Поэтому приоритет не перейдёт к обёрнутому объекту.
>>> order_mock.get_value.return_value = None >>> order_mock.get_value() is None True
Даже если при инициализации mock задать сразу все три значения, порядок приоритета останется прежним:
>>> order_mock = Mock(spec=Order, wraps=Order,
... **{"get_value.side_effect": ["first"],
... "get_value.return_value": "second"}
... )
...
>>> order_mock.get_value()
'first'
>>> order_mock.get_value.side_effect = None
>>> order_mock.get_value()
'second'
>>> order_mock.get_value.return_value = DEFAULT
>>> order_mock.get_value()
'third'
Если значения side_effect закончатся, порядок приоритета не приведёт к получению значения от следующих элементов. Вместо этого будет вызвано исключение StopIteration.
>>> order_mock = Mock(spec=Order, wraps=Order) >>> order_mock.get_value.side_effect = ["first side effect value", ... "another side effect value"] >>> order_mock.get_value.return_value = "second"
>>> order_mock.get_value() 'first side effect value' >>> order_mock.get_value() 'another side effect value'
>>> order_mock.get_value() Traceback (most recent call last): ... StopIteration
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/unittest.mock.html