unittest.mock — библиотека объектов-заглушек
Новая в версии 3.3.
Исходный код: Lib/unittest/mock.py
unittest.mock — это библиотека для тестирования на Python. Она позволяет заменить части вашей тестируемой системы объектами-заглушками и делать утверждения о том, как они были использованы.
unittest.mock предоставляет базовый класс Mock, устраняя необходимость создавать множество заглушек в вашем наборе тестов. После выполнения действия вы можете сделать утверждения о том, какие методы/атрибуты были использованы и с какими аргументами они были вызваны. Вы также можете указать возвращаемые значения и установить необходимые атрибуты обычным способом.
Кроме того, mock предоставляет декоратор patch(), который обрабатывает замену атрибутов модулей и классов в рамках теста, а также sentinel для создания уникальных объектов. См. краткое руководство для примеров использования Mock, MagicMock и patch().
Mock предназначен для использования с unittest и основан на шаблоне «действие → утверждение» вместо «запись → воспроизведение», используемом во многих фреймворках для создания заглушек.
Есть портабельный вариант unittest.mock для более ранних версий Python, доступный как 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 позволяет вам выполнять побочные эффекты, включая возбуждение исключения при вызове объекта-заглушки:
>>> 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__.
Класс 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. Полезно для поднятия исключений или динамического изменения возвращаемых значений. Функция вызывается с теми же аргументами, что и mock, и, если она не возвращаетDEFAULT, возвращаемое значение этой функции используется в качестве возвращаемого значения.В качестве альтернативы side_effect может быть классом или экземпляром исключения. В этом случае исключение будет поднято при вызове mock.
Если side_effect является итерируемым объектом, каждый вызов mock вернёт следующее значение из итерируемого объекта.
side_effect можно очистить, установив его в значение
None. -
return_value: Значение, возвращаемое при вызове mock. По умолчанию это новый Mock (созданный при первом обращении). См. атрибут
return_value. -
unsafe: По умолчанию, если любой атрибут начинается с assert или assret, будет выброшено
AttributeError. Передача значенияunsafe=Trueпозволит получить доступ к этим атрибутам.Добавлена в версии 3.5.
-
wraps: Объект, который будет обернут объектом mock. Если wraps не
None, то вызов Mock передаст вызов обернутому объекту (возвращая реальный результат). Обращение к атрибуту mock вернет объект Mock, который оборачивает соответствующий атрибут обернутого объекта (поэтому попытка доступа к атрибуту, который не существует, вызоветAttributeError).Если у mock явно задано значение return_value, вызовы не передаются обернутому объекту, и вместо этого возвращается return_value.
- name: Если у mock есть имя, оно будет использовано в представлении mock. Это может быть полезно для отладки. Имя передаётся дочерним моделям.
Объекты Mock также могут вызываться с произвольными аргументами ключевых слов. Эти аргументы будут использованы для установки атрибутов на mock после его создания. Подробнее см. метод
configure_mock().-
assert_called() -
Утверждение, что mock был вызван хотя бы один раз.
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called()
Добавлена в версии 3.6.
-
assert_called_once() -
Утверждение, что mock был вызван ровно один раз.
>>> 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 = 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) -
Утверждение, что mock был вызван с указанными аргументами.
Утверждение проходит, если mock был вызван хотя бы один раз, в отличие от
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 был вызван с указанными вызовами. Проверяется список
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() -
Утверждение, что mock никогда не был вызван.
>>> 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 = 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. Дочерние mocks и mock значения возврата (если есть) также сбрасываются.Примечание
return_value и
side_effectявляются аргументами только для ключевых слов.
-
mock_add_spec(spec, spec_set=False) -
Добавить спецификацию к mock. spec может быть объектом или списком строк. Только атрибуты в spec могут быть получены как атрибуты из mock.
Если spec_set истинно, то только атрибуты из spec могут быть установлены.
-
attach_mock(mock, attribute) -
Прикрепить mock как атрибут к этому mock, заменив его имя и родителя. Вызовы прикреплённого mock будут записаны в атрибуты
method_callsиmock_callsэтого mock.
-
configure_mock(**kwargs) -
Установить атрибуты на mock с помощью аргументов ключевых слов.
Атрибуты, а также значения возврата и побочные эффекты можно установить на дочерние mocks, используя стандартную нотацию точки и распаковку словаря в вызове метода:
>>> 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То же самое можно сделать в вызове конструктора для mocks:
>>> 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): ... KeyErrorconfigure_mock()создан для упрощения конфигурирования после создания mock.
-
__dir__() -
Объекты
Mockограничивают результатыdir(some_mock)полезными результатами. Для mocks со свойством spec это включает все разрешенные атрибуты для mock.См.
FILTER_DIRдля того, что делает эта фильтрация и как её отключить.
-
_get_child_mock(**kw) -
Создать дочерние mocks для атрибутов и значения возврата. По умолчанию дочерние mocks будут того же типа, что и родитель. Подклассы Mock могут переопределить это, чтобы настроить способ создания дочерних mocks.
Для невызываемых mocks будет использоваться вызываемый вариант (а не любой пользовательский подкласс).
-
called -
Булево значение, указывающее, был ли вызван объект mock:
>>> mock = Mock(return_value=None) >>> mock.called False >>> mock() >>> mock.called True
-
call_count -
Целое число, показывающее, сколько раз был вызван объект mock:
>>> mock = Mock(return_value=None) >>> mock.call_count 0 >>> mock() >>> mock() >>> mock.call_count 2
-
-
return_value -
Установите это значение для настройки возвращаемого значения при вызове мока:
>>> mock = Mock() >>> mock.return_value = 'fish' >>> mock() 'fish'
По умолчанию возвращаемое значение — это объект мока, который можно настроить обычным способом:
>>> mock = Mock() >>> mock.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(если мок ещё не вызывался), либо аргументы, с которыми мок был вызван в последний раз. Это будет в форме кортежа: первый элемент, к которому также можно обратиться через свойство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_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) -
Мок, предназначенный для использования в качестве свойства или другого дескриптора в классе.
PropertyMockпредоставляет методы__get__()и__set__(), чтобы вы могли указать возвращаемое значение при обращении к нему.Получение экземпляра
PropertyMockиз объекта вызывает мок без аргументов. Установка вызывает мок с устанавливаемым значением.>>> class Foo: ... @property ... def foo(self): ... return 'something' ... @foo.setter ... def foo(self, value): ... pass ... >>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo: ... mock_foo.return_value = 'mockity-mock' ... this_foo = Foo() ... print(this_foo.foo) ... this_foo.foo = 6 ... mockity-mock >>> mock_foo.mock_calls [call(), call(6)]
Из-за способа хранения атрибутов мока, вы не можете напрямую прикрепить PropertyMock к объекту мока. Вместо этого вы можете прикрепить его к объекту типа мока:
>>> m = MagicMock() >>> p = PropertyMock(return_value=3) >>> type(m).foo = p >>> m.foo 3 >>> p.assert_called_once_with()
-
class unittest.mock.AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs) -
Асинхронная версия
MagicMock. ОбъектAsyncMockбудет вести себя таким образом, что объект распознаётся как асинхронная функция, а результат вызова — это awaitable.>>> mock = AsyncMock() >>> asyncio.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(если родительский мок —AsyncMockилиMagicMock) илиMock(если родительский мок —Mock). Все асинхронные функции будутAsyncMock.>>> class ExampleClass: ... def sync_foo(): ... pass ... async def async_foo(): ... pass ... >>> a_mock = AsyncMock(ExampleClass) >>> a_mock.sync_foo <MagicMock name='mock.sync_foo' id='...'> >>> a_mock.async_foo <AsyncMock name='mock.async_foo' id='...'> >>> mock = Mock(ExampleClass) >>> mock.sync_foo <Mock name='mock.sync_foo' id='...'> >>> mock.async_foo <AsyncMock name='mock.async_foo' id='...'>
Новое в версии 3.8.
-
assert_awaited() -
Утверждение, что мок был ожидан по меньшей мере один раз. Обратите внимание, что это отделено от вызова объекта, ключевое слово
awaitдолжно быть использовано:>>> mock = AsyncMock() >>> async def main(coroutine_mock): ... await coroutine_mock ... >>> coroutine_mock = mock() >>> mock.called True >>> mock.assert_awaited() Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited. >>> asyncio.run(main(coroutine_mock)) >>> mock.assert_awaited()
-
assert_awaited_once() -
Утверждение, что мок был ожидан ровно один раз.
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.assert_awaited_once() >>> asyncio.run(main()) >>> mock.method.assert_awaited_once() Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited once. Awaited 2 times.
-
assert_awaited_with(*args, **kwargs) -
Утверждение, что последнее ожидание было с указанными аргументами.
>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> asyncio.run(main('foo', bar='bar')) >>> mock.assert_awaited_with('foo', bar='bar') >>> mock.assert_awaited_with('other') Traceback (most recent call last): ... AssertionError: expected call not found. Expected: mock('other') Actual: mock('foo', bar='bar')
-
assert_awaited_once_with(*args, **kwargs) -
Утверждение, что мок был ожидан ровно один раз и с указанными аргументами.
>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> asyncio.run(main('foo', bar='bar')) >>> mock.assert_awaited_once_with('foo', bar='bar') >>> asyncio.run(main('foo', bar='bar')) >>> mock.assert_awaited_once_with('foo', bar='bar') Traceback (most recent call last): ... AssertionError: Expected mock to have been awaited once. Awaited 2 times.
-
assert_any_await(*args, **kwargs) -
Утверждение, что мок когда-либо был ожидан с указанными аргументами.
>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> asyncio.run(main('foo', bar='bar')) >>> asyncio.run(main('hello')) >>> mock.assert_any_await('foo', bar='bar') >>> mock.assert_any_await('other') Traceback (most recent call last): ... AssertionError: mock('other') await not found
-
assert_has_awaits(calls, any_order=False) -
Утверждение, что мок был ожидан с указанными вызовами. Проверяется список
await_args_listожидания.Если any_order ложно, ожидания должны быть последовательными. Может быть дополнительные вызовы до или после указанных ожиданий.
Если any_order истинно, ожидания могут быть в любом порядке, но все они должны появиться в
await_args_list.>>> mock = AsyncMock() >>> async def main(*args, **kwargs): ... await mock(*args, **kwargs) ... >>> calls = [call("foo"), call("bar")] >>> mock.assert_has_awaits(calls) Traceback (most recent call last): ... AssertionError: Awaits not found. Expected: [call('foo'), call('bar')] Actual: [] >>> asyncio.run(main('foo')) >>> asyncio.run(main('bar')) >>> mock.assert_has_awaits(calls)
-
assert_not_awaited() -
Утверждение, что мок никогда не был ожидан.
>>> mock = AsyncMock() >>> mock.assert_not_awaited()
-
reset_mock(*args, **kwargs) -
См.
Mock.reset_mock(). Также устанавливаетawait_countв 0,await_argsв None и очищаетawait_args_list.
-
await_count -
Целое число, отслеживающее, сколько раз был ожидан объект мока.
>>> mock = AsyncMock() >>> async def main(): ... await mock() ... >>> asyncio.run(main()) >>> mock.await_count 1 >>> asyncio.run(main()) >>> mock.await_count 2
-
await_args -
Это либо
None(если мок не был ожидан), либо аргументы, с которыми мок был последним ожидан. Функционирует аналогичноMock.call_args.>>> mock = AsyncMock() >>> async def main(*args): ... await mock(*args) ... >>> mock.await_args >>> asyncio.run(main('foo')) >>> mock.await_args call('foo') >>> asyncio.run(main('bar')) >>> mock.await_args call('bar')
-
await_args_list -
Это список всех ожиданий объекта мока в последовательности (так что длина списка равна числу ожиданий). Перед любыми ожиданиями это пустой список.
>>> mock = AsyncMock() >>> async def main(*args): ... await mock(*args) ... >>> mock.await_args_list [] >>> asyncio.run(main('foo')) >>> mock.await_args_list [call('foo')] >>> asyncio.run(main('bar')) >>> mock.await_args_list [call('foo'), call('bar')]
- если
Вызов
Объекты мока вызываемы. Вызов вернёт значение, установленное как атрибут return_value. Значение по умолчанию — новый объект Mock; он создаётся при первом обращении к возвращаемому значению (явном или при вызове Mock) — но он сохраняется и возвращается каждый раз.
Вызовы объекта будут записаны в атрибутах, таких как call_args и call_args_list.
Если side_effect установлено, оно будет вызываться после того, как вызов был записан. Таким образом, если side_effect поднимет исключение, вызов всё ещё будет записан.
Простейший способ заставить мок поднять исключение при вызове — сделать side_effect классом или экземпляром исключения:
>>> m = MagicMock(side_effect=IndexError)
>>> m(1, 2, 3)
Traceback (most recent call last):
...
IndexError
>>> m.mock_calls
[call(1, 2, 3)]
>>> m.side_effect = KeyError('Bang!')
>>> m('two', 'three', 'four')
Traceback (most recent call last):
...
KeyError: 'Bang!'
>>> m.mock_calls
[call(1, 2, 3), call('two', 'three', 'four')]
Если side_effect является функцией, то возвращаемое значение этой функции — это возвращаемое значение вызовов мока. Функция side_effect вызывается с теми же аргументами, что и мок. Это позволяет динамически изменять возвращаемое значение вызова в зависимости от ввода:
>>> def side_effect(value): ... return value + 1 ... >>> m = MagicMock(side_effect=side_effect) >>> m(1) 2 >>> m(2) 3 >>> m.mock_calls [call(1), call(2)]
Если вы хотите, чтобы мок всё ещё возвращал значение по умолчанию (новый мок) или любое заданное возвращаемое значение, есть два способа сделать это. Либо вернуть mock.return_value изнутри side_effect, либо вернуть DEFAULT:
>>> m = MagicMock() >>> def side_effect(*args, **kwargs): ... return m.return_value ... >>> m.side_effect = side_effect >>> m.return_value = 3 >>> m() 3 >>> def side_effect(*args, **kwargs): ... return DEFAULT ... >>> m.side_effect = side_effect >>> m() 3
Чтобы удалить side_effect и вернуться к поведению по умолчанию, установите side_effect в None:
>>> m = MagicMock(return_value=6) >>> def side_effect(*args, **kwargs): ... return 3 ... >>> m.side_effect = side_effect >>> m() 3 >>> m.side_effect = None >>> m() 6
side_effect также может быть любым итерируемым объектом. Повторные вызовы мока будут возвращать значения из итерируемого объекта (пока итерируемый объект не исчерпан и не поднимется 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
Удаление атрибутов
Объекты-модели создают атрибуты по запросу. Это позволяет им имитировать объекты любого типа.
Возможно, вам нужно, чтобы объект-модель возвращал False в вызове hasattr() или генерировал исключение AttributeError, когда запрашивается атрибут. Вы можете сделать это, предоставив объект в качестве spec для модели, но это не всегда удобно.
Вы «блокируете» атрибуты, удаляя их. После удаления доступ к атрибуту вызовет исключение AttributeError.
>>> mock = MagicMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
...
AttributeError: f
Имена моделей и атрибут name
Поскольку «name» является аргументом конструктора Mock, если вы хотите, чтобы ваш объект-модель имел атрибут «name», вы не можете просто передать его при создании. Существует два варианта. Один из них — использовать configure_mock():
>>> mock = MagicMock() >>> mock.configure_mock(name='my_name') >>> mock.name 'my_name'
Более простой вариант — просто установить атрибут «name» после создания модели:
>>> mock = MagicMock() >>> mock.name = "foo"
Прикрепление моделей в качестве атрибутов
Когда вы прикрепляете модель в качестве атрибута другой модели (или в качестве возвращаемого значения), она становится «дочерней» для этой модели. Вызовы дочерней модели записываются в атрибуты method_calls и mock_calls родительской модели. Это полезно для настройки дочерних моделей и их прикрепления к родительской модели, или для прикрепления моделей к родительской модели, которая записывает все вызовы дочерних моделей и позволяет делать утверждения о порядке вызовов между моделями:
>>> parent = MagicMock() >>> child1 = MagicMock(return_value=None) >>> child2 = MagicMock(return_value=None) >>> parent.child1 = child1 >>> parent.child2 = child2 >>> child1(1) >>> child2(2) >>> parent.mock_calls [call.child1(1), call.child2(2)]
Исключением является случай, если у модели есть имя. Это позволяет предотвратить «наследование», если по какой-либо причине этого не нужно.
>>> mock = MagicMock() >>> not_a_child = MagicMock(name='not-a-child') >>> mock.attribute = not_a_child >>> mock.attribute() <MagicMock name='not-a-child()' id='...'> >>> mock.mock_calls []
Модели, созданные для вас функцией patch(), автоматически получают имена. Чтобы прикрепить модели с именами к родительской модели, используйте метод attach_mock():
>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
... with patch('__main__.thing2', return_value=None) as child2:
... parent.attach_mock(child1, 'child1')
... parent.attach_mock(child2, 'child2')
... child1('one')
... child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]
-
1 -
Исключение составляют магические методы и атрибуты (те, у которых есть ведущие и заключительные двойные нижние подчёркивания). Модель не создаёт их, а вместо этого генерирует исключение
AttributeError. Это происходит потому, что интерпретатор часто неявно запрашивает эти методы и очень сильно запутывается, получая новый объект Mock, когда он ожидает магический метод. Если вам нужна поддержка магических методов, см. магические методы.
Декораторы патчинга
Декораторы патчинга используются для изменения объектов только в пределах области действия функции, которую они декорируют. Они автоматически выполняют отмену патчинга, даже если возникают исключения. Все эти функции также могут использоваться в операторе 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 опущено, то target заменяется на
AsyncMock, если заменяемый объект является асинхронной функцией, или на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 объекта. По умолчанию для асинхронных функций используется
AsyncMock, а для остальных —MagicMock.Более мощная форма spec — autospec. Если вы установите
autospec=True, то mock будет создан со spec от заменяемого объекта. Все атрибуты mock также будут иметь spec соответствующего атрибута заменяемого объекта. Методы и функции, которые имитируются, будут проверять свои аргументы и генерироватьTypeError, если они вызываются с неправильной сигнатурой. Для mock, заменяющих класс, их возвращаемое значение («экземпляр») будет иметь тот же spec, что и класс. См. функциюcreate_autospec()и Автоспецификация.Вместо
autospec=Trueвы можете передатьautospec=some_object, чтобы использовать произвольный объект в качестве spec вместо заменяемого.По умолчанию,
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()принимает произвольные ключевые аргументы. Эти аргументы будут переданы вAsyncMock, если заменяемый объект является асинхронным, вMagicMockв противном случае, или в 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 будет иметь тот же spec.
>>> 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
По умолчанию, попытка патчить функцию в модуле (или метод или атрибут в классе), которая не существует, завершится с 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) -
Заменить указанный член (атрибут) объекта (target) на объект-модель.
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 также может быть итерируемым объектом пар ключ-значение.
Если 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
Все patch-объекты имеют методы 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.
patch builtins
Вы можете подменить любые встроенные функции внутри модуля. Следующий пример подменяет встроенную функцию 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, также известных как «магические методы». Это позволяет объектам-мокам заменять контейнеры или другие объекты, реализующие протоколы Python.
Поскольку магические методы ищутся по-другому, чем обычные методы 2, эта поддержка была специально реализована. Это означает, что поддерживаются только определенные магические методы. Поддерживаемый список включает почти все из них. Если есть какие-то отсутствующие, которые вам нужны, сообщите нам, пожалуйста.
Вы моделируете магические методы, устанавливая интересующий вас метод в функцию или экземпляр мока. Если вы используете функцию, то она должна принимать self в качестве первого аргумента 3.
>>> def __str__(self): ... return 'fooble' ... >>> mock = Mock() >>> mock.__str__ = __str__ >>> str(mock) 'fooble'
>>> mock = Mock() >>> mock.__str__ = Mock() >>> mock.__str__.return_value = 'fooble' >>> str(mock) 'fooble'
>>> mock = Mock() >>> mock.__iter__ = Mock(return_value=iter([])) >>> list(mock) []
Один из вариантов использования этого — для моделирования объектов, используемых в качестве контекстных менеджеров в инструкции with:
>>> mock = Mock() >>> mock.__enter__ = Mock(return_value='foo') >>> mock.__exit__ = Mock(return_value=False) >>> with mock as m: ... assert m == 'foo' ... >>> mock.__enter__.assert_called_with() >>> mock.__exit__.assert_called_with(None, None, None)
Вызовы магических методов не отображаются в method_calls, но записываются в mock_calls.
Примечание
Если вы используете ключевой аргумент spec для создания мока, то попытка установить магический метод, который отсутствует в спецификации, вызовет AttributeError.
Полный список поддерживаемых магических методов:
-
__hash__,__sizeof__,__repr__и__str__ -
__dir__,__format__и__subclasses__ -
__round__,__floor__,__trunc__и__ceil__ - Сравнения:
__lt__,__gt__,__le__,__ge__,__eq__и__ne__ - Методы контейнеров:
__getitem__,__setitem__,__delitem__,__contains__,__len__,__iter__,__reversed__и__missing__ - Контекстный менеджер:
__enter__,__exit__,__aenter__и__aexit__ - Унарные числовые методы:
__neg__,__pos__и__invert__ - Числовые методы (включая варианты правой части и на месте):
__add__,__sub__,__mul__,__matmul__,__div__,__truediv__,__floordiv__,__mod__,__divmod__,__lshift__,__rshift__,__and__,__xor__,__or__и__pow__ - Методы числового преобразования:
__complex__,__int__,__float__и__index__ - Методы дескриптора:
__get__,__set__и__delete__ - Сериализация:
__reduce__,__reduce_ex__,__getinitargs__,__getnewargs__,__getstate__и__setstate__ - Представление пути файловой системы:
__fspath__ - Методы асинхронной итерации:
__aiter__и__anext__
Изменено в версии 3.8: Добавлена поддержка os.PathLike.__fspath__().
Изменено в версии 3.8: Добавлена поддержка __aenter__, __aexit__, __aiter__ и __anext__.
Следующие методы существуют, но не поддерживаются, так как они либо используются моком, либо не могут быть настроены динамически, либо могут вызывать проблемы:
-
__getattr__,__setattr__,__init__и__new__ -
__prepare__,__instancecheck__,__subclasscheck__,__del__
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 -
__aexit__:False -
__complex__:1j -
__float__:1.0 -
__bool__:True -
__index__:1 -
__hash__: хэш по умолчанию для мока -
__str__: строковое представление по умолчанию для мока -
__sizeof__: размер по умолчанию для мока
Например:
>>> mock = MagicMock() >>> int(mock) 1 >>> len(mock) 0 >>> list(mock) [] >>> object() in mock False
Два метода равенства, __eq__() и __ne__(), являются специальными. Они выполняют сравнение по равенству по идентичности по умолчанию, используя атрибут side_effect, если вы не измените их возвращаемое значение на что-то другое:
>>> MagicMock() == 3 False >>> MagicMock() != 3 True >>> mock = MagicMock() >>> mock.__eq__.return_value = True >>> mock == 3 True
Возвращаемое значение MagicMock.__iter__() может быть любым итерируемым объектом и не обязательно итератором:
>>> mock = MagicMock() >>> mock.__iter__.return_value = ['a', 'b', 'c'] >>> list(mock) ['a', 'b', 'c'] >>> list(mock) ['a', 'b', 'c']
Если возвращаемое значение является итератором, то перебор его один раз использует его, а последующие переборы приведут к пустому списку:
>>> mock.__iter__.return_value = iter(['a', 'b', 'c']) >>> list(mock) ['a', 'b', 'c'] >>> list(mock) []
MagicMock имеет все поддерживаемые магические методы, настроенные, за исключением некоторых устаревших.
Магические методы, которые поддерживаются, но не настроены по умолчанию в MagicMock:
__subclasses____dir____format__-
__get__,__set__и__delete__ -
__reversed__и__missing__ -
__reduce__,__reduce_ex__,__getinitargs__,__getnewargs__,__getstate__и__setstate__ -
__getformat__и__setformat__
-
2 -
Магические методы должны ищется в классе, а не в экземпляре. Различные версии Python несовместимы в отношении применения этого правила. Поддерживаемые методы протокола должны работать со всеми поддерживаемыми версиями Python.
-
3 -
Функция в основном подключается к классу, но каждый экземпляр
Mockизолирован от других.
Справочные методы
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 на имитации. Ручное построение последовательности вызовов может быть утомительным.
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) -
Создайте объект имитации, используя другой объект в качестве спецификации. Атрибуты имитации будут использовать соответствующий атрибут объекта spec в качестве своей спецификации.
Функции или методы, которые имитируются, будут проверять свои аргументы, чтобы убедиться, что они вызываются с правильной сигнатурой.
Если spec_set — это
True, то попытка установить атрибуты, которые не существуют в объекте спецификации, вызоветAttributeError.Если в качестве спецификации используется класс, то возвращаемое значение имитации (экземпляр класса) будет иметь ту же спецификацию. Вы можете использовать класс в качестве спецификации для экземпляра объекта, передав
instance=True. Возвращаемая имитация будет вызываемой только в том случае, если экземпляры имитации вызываемы.create_autospec()также принимает произвольные ключевые аргументы, которые передаются в конструктор созданной имитации.
См. Автоспецификация для примеров использования автоспецификации с create_autospec() и аргументом autospec для patch().
Изменено в версии 3.8: create_autospec() теперь возвращает AsyncMock, если целевая функция асинхронна.
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
FILTER_DIR
-
unittest.mock.FILTER_DIR
FILTER_DIR — это переменная уровня модуля, которая управляет тем, как объекты имитации реагируют на dir() (только для Python 2.6 или более поздних версий). Значение по умолчанию — True, которое использует фильтрацию, описанную ниже, чтобы показывать только полезные члены. Если вы не хотите использовать эту фильтрацию или вам нужно отключить её для диагностических целей, задайте значение mock.FILTER_DIR = False.
При включённой фильтрации dir(some_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', ...
Многие не очень полезные (частные для Mock, а не для имитируемого объекта) атрибуты, начинающиеся с нижнего подчеркивания и двойного нижнего подчеркивания, были отфильтрованы из результата вызова dir() на Mock. Если вы не хотите этого поведения, вы можете отключить его, задав значение переменной уровня модуля FILTER_DIR:
>>> from unittest import mock >>> mock.FILTER_DIR = False >>> dir(mock.Mock()) ['_NonCallableMock__get_return_value', '_NonCallableMock__get_side_effect', '_NonCallableMock__return_value_doc', '_NonCallableMock__set_return_value', '_NonCallableMock__set_side_effect', '__call__', '__class__', ...
В качестве альтернативы вы можете просто использовать vars(my_mock) (члены экземпляров) и dir(type(my_mock)) (члены типа), чтобы обойти фильтрацию независимо от mock.FILTER_DIR.
mock_open
-
unittest.mock.mock_open(mock=None, read_data=None) -
Вспомогательная функция для создания мока, заменяющего использование
open(). Она работает дляopen(), вызываемого напрямую или используемого в качестве менеджера контекста.Аргумент mock — это объект мока, который необходимо настроить. Если
None(по умолчанию), то для вас будет созданMagicMockс API, ограниченным методами или атрибутами, доступными для стандартных файловых дескрипторов.read_data — это строка для
read(),readline()иreadlines()методов файлового дескриптора для возврата. Вызовы этих методов будут брать данные из read_data, пока оно не исчерпается. Мок этих методов довольно упрощен: каждый раз, когда вызывается mock, read_data перематывается в начало. Если вам нужен больший контроль над данными, которые вы подаёте в тестируемый код, вам нужно будет настроить этот мок самостоятельно. Когда этого недостаточно, пакеты систем памяти на PyPI могут предложить реалистичную файловую систему для тестирования.Изменено в версии 3.4: Добавлена поддержка
readline()иreadlines(). Мокread()изменён на потребление read_data вместо возврата его при каждом вызове.Изменено в версии 3.5: read_data теперь сбрасывается при каждом вызове mock.
Изменено в версии 3.8: Добавлена
__iter__()в реализацию, чтобы итерация (например, в циклах for) корректно потребляла read_data.
Использование open() в качестве менеджера контекста — отличный способ гарантировать правильное закрытие ваших файловых дескрипторов и становится все более распространённым:
with open('/some/path', 'w') as f:
f.write('something')
Проблема в том, что даже если вы замаскируете вызов open(), в качестве менеджера контекста используется возвращаемый объект (и вызываются __enter__() и __exit__()).
Использование моков менеджеров контекста с MagicMock достаточно часто встречается и достаточно сложное, что вспомогательная функция полезна.
>>> m = mock_open()
>>> with patch('__main__.open', m):
... with open('foo', 'w') as h:
... h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
call().__enter__(),
call().write('some stuff'),
call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
И для чтения файлов:
>>> with patch('__main__.open', mock_open(read_data='bibble')) as m:
... with open('foo') as h:
... result = h.read()
...
>>> m.assert_called_once_with('foo')
>>> assert result == 'bibble'
Автоспессинг
Автоспессинг основан на существующей spec функции модуля mock. Он ограничивает API моков API исходного объекта (спецификация), но он рекурсивен (реализован лениво), так что атрибуты моков имеют тот же API, что и атрибуты спецификации. Кроме того, функции/методы моков имеют такую же сигнатуру вызова, как и оригинальные, поэтому они генерируют исключение TypeError, если они вызываются неправильно.
Прежде чем объяснить, как работает автоспессинг, вот почему он нужен.
Mock — это очень мощный и гибкий объект, но при использовании для имитации объектов в тестируемой системе у него есть два недостатка. Один из них специфичен для 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) # Intentional typo!
Ваши тесты могут проходить молча и неправильно из-за опечатки.
Вторая проблема более общая для имитации. Если вы перефакторили часть своего кода, переименовали члены и т. д., все тесты для кода, который все еще использует старый API, но использует моки вместо реальных объектов, все равно пройдут. Это означает, что все ваши тесты могут пройти, даже если ваш код сломан.
Обратите внимание, что это еще одна причина, по которой вам нужны интеграционные тесты наряду с модульными. Тестирование всего изолированно — хорошо, но если вы не тестируете, как ваши модули «подключены друг к другу», то останется много места для ошибок, которые могли бы быть пойманы тестами.
mock уже предоставляет функцию для решения этой проблемы под названием спецификация. Если вы используете класс или экземпляр в качестве spec для мока, то вы можете получить доступ только к атрибутам мока, которые существуют в реальном классе:
>>> from urllib import request >>> mock = Mock(spec=request.Request) >>> mock.assret_called_with # Intentional typo! Traceback (most recent call last): ... AttributeError: Mock object has no attribute 'assret_called_with'
Спецификация применяется только к самому моку, поэтому у нас все еще та же проблема со всеми методами мока:
>>> mock.has_data() <mock.Mock object at 0x...> >>> mock.has_data.assret_called_with() # Intentional typo!
Автоспессинг решает эту проблему. Вы можете либо передать autospec=True в patch() / patch.object(), либо использовать функцию create_autospec() для создания мока со спецификацией. Если вы используете аргумент autospec=True для patch(), то объект, который заменяется, будет использоваться в качестве объекта спецификации. Поскольку спецификация выполняется «лениво» (спецификация создается при обращении к атрибутам мока), вы можете использовать ее с очень сложными или глубоко вложенными объектами (например, модулями, которые импортируют модули, которые импортируют модули), не сильно снижая производительность.
Вот пример использования:
>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>
Вы можете увидеть, что request.Request имеет спецификацию. request.Request принимает два аргумента в конструкторе (один из которых — self). Вот что произойдет, если мы попытаемся вызвать его неправильно:
>>> req = request.Request() Traceback (most recent call last): ... TypeError: <lambda>() takes at least 2 arguments (1 given)
Спецификация также применяется к экземплярам классов (т. е. возвращаемому значению специфицированных моков):
>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Объекты Request не вызываемы, поэтому возвращаемое значение создания экземпляра нашего имитируемого request.Request является невызываемым моком. При наличии спецификации все опечатки в наших утверждениях будут генерировать правильную ошибку:
>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with # Intentional typo!
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')
Во многих случаях вам просто нужно добавить autospec=True к вашим существующим вызовам patch(), чтобы защититься от ошибок из-за опечаток и изменений API.
Помимо использования autospec через patch(), существует create_autospec() для прямого создания имитированных моков:
>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
Однако это не лишено недостатков и ограничений, поэтому это не является стандартным поведением. Для того, чтобы знать, какие атрибуты доступны в объекте спецификации, автоспессинг должен интроспектировать (получать доступ к атрибутам) спецификацию. По мере прохождения по атрибутам мока происходит соответствующее прохождение по исходному объекту под капотом. Если какие-либо из ваших специфицированных объектов имеют свойства или дескрипторы, которые могут вызывать выполнение кода, вы можете не иметь возможности использовать автоспессинг. С другой стороны, лучше спроектировать свои объекты так, чтобы интроспекция была безопасной 4.
Более серьезная проблема заключается в том, что атрибуты экземпляров часто создаются в методе __init__() и вообще не существуют в классе. autospec не может знать о динамически созданных атрибутах и ограничивает API доступными атрибутами.
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
Есть несколько способов решения этой проблемы. Самый простой, но не обязательно самый удобный способ — просто установить необходимые атрибуты в моке после его создания. Просто потому, что autospec не позволяет получить доступ к атрибутам, которых нет в спецификации, это не мешает вам их установить:
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a = 33
...
Существует более агрессивная версия как spec, так и autospec, которая запрещает вам устанавливать несуществующие атрибуты. Это полезно, если вы хотите убедиться, что ваш код устанавливает только допустимые атрибуты, но, очевидно, это предотвращает этот конкретный сценарий:
>>> with patch('__main__.Something', autospec=True, spec_set=True):
... thing = Something()
... thing.a = 33
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
Вероятно, лучший способ решить эту проблему — добавить атрибуты класса в качестве значений по умолчанию для членов экземпляров, инициализированных в __init__(). Обратите внимание, что если вы устанавливаете только атрибуты по умолчанию в __init__(), то предоставление их через атрибуты класса (общие для всех экземпляров, конечно) также быстрее. Например:
class Something:
a = 33
Это поднимает еще одну проблему. Довольно часто для членов, которые впоследствии будут объектами другого типа, предоставляется значение по умолчанию None. None было бы бесполезно в качестве спецификации, потому что это не позволило бы вам получить доступ к любым атрибутам или методам. Поскольку None никогда не будет полезен в качестве спецификации и, вероятно, указывает на член, который обычно является другим типом, autospec не использует спецификацию для членов, установленных в None. Они просто будут обычными моками (ну, MagicMocks):
>>> class Something: ... member = None ... >>> mock = create_autospec(Something) >>> mock.member.foo.bar.baz() <MagicMock name='mock.member.foo.bar.baz()' id='...'>
Если изменение ваших производственных классов для добавления значений по умолчанию вам не подходит, есть больше вариантов. Один из них — использовать экземпляр вместо класса в качестве спецификации. Другой — создать подкласс производственного класса и добавить значения по умолчанию в подкласс без изменения производственного класса. Оба эти варианта требуют использования другого объекта в качестве спецификации. К счастью, patch() поддерживает это — вы можете просто передать альтернативный объект в качестве аргумента autospec:
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> class SomethingForTest(Something):
... a = 33
...
>>> p = patch('__main__.Something', autospec=SomethingForTest)
>>> mock = p.start()
>>> mock.a
<NonCallableMagicMock name='Something.a' spec='int' id='...'>
-
4 -
Это относится только к классам или уже созданным объектам. Вызов имитируемого класса для создания экземпляра мока не создает реальный экземпляр. Это только поиск атрибутов — наряду с вызовами
dir()— которые выполняются.
Запечатывание моков
-
unittest.mock.seal(mock) -
Запечатывание отключит автоматическое создание моков при обращении к атрибуту запечатанного мока или к любому его атрибуту, который уже является моком рекурсивно.
Если экземпляр мока с именем или спецификацией назначается атрибуту, он не будет учитываться в цепочке запечатывания. Это позволяет предотвратить исправление части объекта мока.
>>> 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.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/library/unittest.mock.html