unittest.mock — библиотека создания объектов-моков
Новая в версии 3.3.
Исходный код: Lib/unittest/mock.py
unittest.mock — это библиотека для тестирования на Python. Она позволяет заменить части вашей тестируемой системы объектами-моками и делать утверждения о том, как они были использованы.
unittest.mock предоставляет базовый класс Mock, устраняя необходимость создавать множество заглушек (stubs) в вашем наборе тестов. После выполнения действия вы можете сделать утверждения о том, какие методы/атрибуты были использованы и с какими аргументами они были вызваны. Также вы можете указать возвращаемые значения и установить необходимые атрибуты обычным способом.
Кроме того, mock предоставляет декоратор patch(), который обрабатывает подмену атрибутов модуля и класса в рамках теста, а также sentinel для создания уникальных объектов. См. краткое руководство для примеров использования Mock, MagicMock и patch().
Mock очень прост в использовании и предназначен для использования с unittest. Mock основан на шаблоне «действие —> утверждение», а не на шаблоне «запись —> воспроизведение», используемом многими фреймворками для создания моков.
Существует обратная переносимость 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: Это может быть список строк или существующий объект (класс или экземпляр), который служит спецификацией для объекта подделки. Если вы передаёте объект, то список строк формируется путём вызова dir на объекте (исключая недопустимые магические атрибуты и методы). Обращение к любому атрибуту, не входящему в этот список, вызовет
AttributeError.Если spec является объектом (а не списком строк), то
__class__возвращает класс объекта spec. Это позволяет подделкам проходить проверкиisinstance(). -
spec_set: Более строгая вариация spec. Если используется, попытка установить или получить атрибут объекта подделки, который не присутствует в переданном объекте spec_set, вызовет
AttributeError. -
side_effect: Функция, вызываемая всякий раз, когда вызывается Mock. См. атрибут
side_effect. Полезно для возбуждения исключений или динамического изменения значений возврата. Функция вызывается с теми же аргументами, что и подделка, и, если она не возвращаетDEFAULT, значение возврата этой функции используется как возвращаемое значение.В качестве альтернативы, side_effect может быть классом исключения или экземпляром исключения. В этом случае исключение будет возбуждено при вызове подделки.
Если side_effect является итерируемым объектом, каждый вызов подделки вернёт следующее значение из итерируемого объекта.
side_effect может быть очищен, установив его значение в
None. -
return_value: Значение, возвращаемое при вызове подделки. По умолчанию это новый Mock (создаётся при первом обращении). См. атрибут
return_value. -
unsafe: По умолчанию, если любой атрибут начинается с assert или assret, будет возбуждаться
AttributeError. Передачаunsafe=Trueпозволит получить доступ к этим атрибутам.Добавлено в версии 3.5.
-
wraps: Объект, который объект подделки должен обернуть. Если wraps не
None, то вызов подделки передаст вызов обернутому объекту (возвращая реальный результат). Обращение к атрибуту подделки вернёт объект Mock, который обернёт соответствующий атрибут обернутого объекта (попытка доступа к атрибуту, который не существует, вызоветAttributeError).Если у подделки явно задано return_value, вызовы не передаются обернутому объекту, а возвращается return_value.
- name: Если у подделки есть имя, оно будет использовано в представлении подделки. Это может быть полезно для отладки. Имя передаётся дочерним подделкам.
Подделки также могут вызываться с произвольными именованными аргументами. Эти аргументы будут использованы для установки атрибутов на подделке после её создания. Подробнее см. метод
configure_mock().-
assert_called() -
Утверждение, что подделка была вызвана хотя бы один раз.
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called()
Добавлено в версии 3.6.
-
assert_called_once() -
Утверждение, что подделка была вызвана ровно один раз.
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_once() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_once() Traceback (most recent call last): ... AssertionError: Expected 'method' to have been called once. Called 2 times.
Добавлено в версии 3.6.
-
assert_called_with(*args, **kwargs) -
Этот метод является удобным способом утверждения, что вызовы осуществляются определённым образом:
>>> mock = Mock() >>> mock.method(1, 2, 3, test='wow') <Mock name='mock.method()' id='...'> >>> mock.method.assert_called_with(1, 2, 3, test='wow')
-
assert_called_once_with(*args, **kwargs) -
Утверждение, что подделка была вызвана ровно один раз и с указанными аргументами.
>>> mock = Mock(return_value=None) >>> mock('foo', bar='baz') >>> mock.assert_called_once_with('foo', bar='baz') >>> mock('other', bar='values') >>> mock.assert_called_once_with('other', bar='values') Traceback (most recent call last): ... AssertionError: Expected 'mock' to be called once. Called 2 times.
-
assert_any_call(*args, **kwargs) -
убедиться, что подделка была вызвана с указанными аргументами.
Утверждение проходит, если подделка была вызвана хотя бы один раз, в отличие от
assert_called_with()иassert_called_once_with(), которые проходят только если вызов является последним, а в случаеassert_called_once_with()он также должен быть единственным вызовом.>>> mock = Mock(return_value=None) >>> mock(1, 2, arg='thing') >>> mock('some', 'thing', 'else') >>> mock.assert_any_call(1, 2, arg='thing')
-
assert_has_calls(calls, any_order=False) -
Утверждение, что подделка была вызвана с указанными вызовами. Проверяется список
mock_calls.Если any_order ложно, вызовы должны быть последовательными. До или после указанных вызовов могут быть дополнительные вызовы.
Если any_order истинно, вызовы могут быть в любом порядке, но они все должны присутствовать в
mock_calls.>>> mock = Mock(return_value=None) >>> mock(1) >>> mock(2) >>> mock(3) >>> mock(4) >>> calls = [call(2), call(3)] >>> mock.assert_has_calls(calls) >>> calls = [call(4), call(2), call(3)] >>> mock.assert_has_calls(calls, any_order=True)
-
assert_not_called() -
Утверждение, что подделка никогда не вызывалась.
>>> m = Mock() >>> m.hello.assert_not_called() >>> obj = m.hello() >>> m.hello.assert_not_called() Traceback (most recent call last): ... AssertionError: Expected 'hello' to not have been called. Called 1 times.
Добавлено в версии 3.5.
-
reset_mock(*, return_value=False, side_effect=False) -
Метод reset_mock сбрасывает все атрибуты вызовов объекта подделки:
>>> mock = Mock(return_value=None) >>> mock('hello') >>> mock.called True >>> mock.reset_mock() >>> mock.called FalseИзменено в версии 3.6: Добавлены два именованных аргумента в функцию reset_mock.
Это может быть полезно, когда вы хотите выполнить серию утверждений, которые повторно используют один и тот же объект. Обратите внимание, что
reset_mock()не очищает значение возврата,side_effectили любые дочерние атрибуты, которые вы установили с помощью обычной присваивания по умолчанию. Если вы хотите сбросить return_value илиside_effect, передайте соответствующий параметр какTrue. Дочерние подделки и подделка возвращаемого значения (если таковая есть) также сбрасываются.Примечание
return_value и
side_effectявляются именованными аргументами.
-
mock_add_spec(spec, spec_set=False) -
Добавление спецификации к подделке. spec может быть объектом или списком строк. Только атрибуты в spec могут быть извлечены в качестве атрибутов из подделки.
Если spec_set истинно, то устанавливать можно только атрибуты из spec.
-
attach_mock(mock, attribute) -
Присоединение подделки в качестве атрибута к этому объекту, заменяя его имя и родителя. Вызовы прикреплённой подделки будут записаны в атрибутах
method_callsиmock_callsэтого объекта.
-
configure_mock(**kwargs) -
Установка атрибутов подделки через именованные аргументы.
Атрибуты, значения возврата и side effects могут быть установлены на дочерних подделках с использованием стандартной нотации точек и распаковки словаря в вызове метода:
>>> mock = Mock() >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} >>> mock.configure_mock(**attrs) >>> mock.method() 3 >>> mock.other() Traceback (most recent call last): ... KeyErrorТо же самое можно сделать в вызове конструктора для подделок:
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} >>> mock = Mock(some_attribute='eggs', **attrs) >>> mock.some_attribute 'eggs' >>> mock.method() 3 >>> mock.other() Traceback (most recent call last): ... KeyErrorconfigure_mock()упрощает конфигурацию после создания подделки.
-
__dir__() -
Объекты
Mockограничивают результатыdir(some_mock)полезными результатами. Для подделок со spec это включает все разрешённые атрибуты для подделки.См.
FILTER_DIRдля получения информации о том, что делает это фильтрование, и как его отключить.
-
_get_child_mock(**kw) -
Создание дочерних подделок для атрибутов и значений возврата. По умолчанию дочерние подделки будут того же типа, что и родитель. Подклассы Mock могут переопределять этот метод для настройки способа создания дочерних подделок.
Для невызываемых подделок будет использоваться вызываемый вариант (а не любой пользовательский подкласс).
-
called -
Булево значение, указывающее, был ли вызван объект подделки:
>>> mock = Mock(return_value=None) >>> mock.called False >>> mock() >>> mock.called True
-
call_count -
Целое число, показывающее, сколько раз был вызван объект подделки:
>>> mock = Mock(return_value=None) >>> mock.call_count 0 >>> mock() >>> mock() >>> mock.call_count 2
-
-
return_value -
Установите это значение для настройки возвращаемого значения при вызове мока:
>>> mock = Mock() >>> mock.return_value = 'fish' >>> mock() 'fish'
По умолчанию возвращаемое значение — объект-мока, и его можно настроить обычным способом:
>>> mock = Mock() >>> mock.return_value.attribute = sentinel.Attribute >>> mock.return_value() <Mock name='mock()()' id='...'> >>> mock.return_value.assert_called_with()
return_valueтакже можно установить в конструкторе:>>> mock = Mock(return_value=3) >>> mock.return_value 3 >>> mock() 3
-
side_effect -
Это может быть функция, которая вызывается при вызове мока, итерируемый объект или исключение (класс или экземпляр), которое должно быть поднято.
Если вы передадите функцию, она будет вызвана с теми же аргументами, что и мок, и, если функция не вернёт синглетон
DEFAULT, вызов мока вернёт то, что вернёт функция. Если функция вернётDEFAULT, то мок вернёт своё нормальное значение (изreturn_value).Если вы передадите итерируемый объект, он будет использован для получения итератора, который должен возвращать значение при каждом вызове. Это значение может быть либо экземпляром исключения, которое должно быть поднято, либо значением, которое должно быть возвращено при вызове мока (
DEFAULTобработка идентична случаю с функцией).Пример мока, который поднимает исключение (для тестирования обработки исключений API):
>>> mock = Mock() >>> mock.side_effect = Exception('Boom!') >>> mock() Traceback (most recent call last): ... Exception: Boom!Использование
side_effectдля возвращения последовательности значений:>>> mock = Mock() >>> mock.side_effect = [3, 2, 1] >>> mock(), mock(), mock() (3, 2, 1)
Использование вызываемого объекта:
>>> mock = Mock(return_value=3) >>> def side_effect(*args, **kwargs): ... return DEFAULT ... >>> mock.side_effect = side_effect >>> mock() 3
side_effectможно установить в конструкторе. Вот пример, который добавляет единицу к значению, с которым вызывается мок, и возвращает его:>>> side_effect = lambda value: value + 1 >>> mock = Mock(side_effect=side_effect) >>> mock(3) 4 >>> mock(-8) -7
Установка
side_effectвNoneочищает его:>>> m = Mock(side_effect=KeyError, return_value=3) >>> m() Traceback (most recent call last): ... KeyError >>> m.side_effect = None >>> m() 3
-
call_args -
Это либо
None(если мок не вызывался), либо аргументы, с которыми мок был вызван последний раз. Это будет в форме кортежа: первый член — упорядоченные аргументы, с которыми был вызван мок (или пустой кортеж), а второй член — произвольные именованные аргументы (или пустой словарь).>>> mock = Mock(return_value=None) >>> print(mock.call_args) None >>> mock() >>> mock.call_args call() >>> mock.call_args == () True >>> mock(3, 4) >>> mock.call_args call(3, 4) >>> mock.call_args == ((3, 4),) True >>> mock(3, 4, 5, key='fish', next='w00t!') >>> mock.call_args call(3, 4, 5, key='fish', next='w00t!')
call_args, наряду с членами списковcall_args_list,method_callsиmock_calls— объектыcall. Это кортежи, поэтому их можно распаковать, чтобы получить отдельные аргументы и создать более сложные утверждения. См. вызовы в виде кортежей.
-
call_args_list -
Это список всех вызовов объекта-мока в последовательности (так что длина списка — количество раз, когда он был вызван). Перед любыми вызовами он пустой. Объект
callможно использовать для удобного построения списков вызовов для сравнения сcall_args_list.>>> mock = Mock(return_value=None) >>> mock() >>> mock(3, 4) >>> mock(key='fish', next='w00t!') >>> mock.call_args_list [call(), call(3, 4), call(key='fish', next='w00t!')] >>> expected = [(), ((3, 4),), ({'key': 'fish', 'next': 'w00t!'},)] >>> mock.call_args_list == expected TrueЧлены
call_args_list— объектыcall. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.
-
method_calls -
Помимо отслеживания вызовов к себе, моки также отслеживают вызовы методов и атрибутов, а также их методов и атрибутов:
>>> mock = Mock() >>> mock.method() <Mock name='mock.method()' id='...'> >>> mock.property.method.attribute() <Mock name='mock.property.method.attribute()' id='...'> >>> mock.method_calls [call.method(), call.property.method.attribute()]
Члены
method_calls— объектыcall. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.
-
mock_calls -
mock_callsзаписывает все вызовы объекта-мока, его методов, магических методов и моков возвращаемых значений.>>> mock = MagicMock() >>> result = mock(1, 2, 3) >>> mock.first(a=3) <MagicMock name='mock.first()' id='...'> >>> mock.second() <MagicMock name='mock.second()' id='...'> >>> int(mock) 1 >>> result(1) <MagicMock name='mock()()' id='...'> >>> expected = [call(1, 2, 3), call.first(a=3), call.second(), ... call.__int__(), call()(1)] >>> mock.mock_calls == expected True
Члены
mock_calls— объектыcall. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.Примечание
Способ записи
mock_callsозначает, что при вложенных вызовах параметры родительских вызовов не записываются, и поэтому всегда будут сравниваться как равные:>>> mock = MagicMock() >>> mock.top(a=3).bottom() <MagicMock name='mock.top().bottom()' id='...'> >>> mock.mock_calls [call.top(a=3), call.top().bottom()] >>> mock.mock_calls[-1] == call.top(a=-1).bottom() True
-
__class__ -
Обычно атрибут
__class__объекта возвращает его тип. Для объекта-мока со спецификациейspec,__class__возвращает класс спецификации вместо этого. Это позволяет объектам-мокам проходить тестыisinstance()для объекта, который они заменяют/маскируют:>>> mock = Mock(spec=3) >>> isinstance(mock, int) True
__class__назначается, это позволяет моку пройти проверкуisinstance()без принуждения к использованию спецификации:>>> mock = Mock() >>> mock.__class__ = dict >>> isinstance(mock, dict) True
-
-
class unittest.mock.NonCallableMock(spec=None, wraps=None, name=None, spec_set=None, **kwargs) -
Невызываемый вариант
Mock. Параметры конструктора имеют то же значение, что и уMock, за исключением return_value и side_effect, которые не имеют значения для невызываемого мока.
Объекты-моки, использующие класс или экземпляр в качестве spec или spec_set, могут проходить тесты isinstance():
>>> mock = Mock(spec=SomeClass) >>> isinstance(mock, SomeClass) True >>> mock = Mock(spec_set=SomeClass()) >>> isinstance(mock, SomeClass) True
Классы Mock поддерживают имитацию магических методов. Подробности см. в разделе магические методы.
Классы моков и декораторы patch() принимают произвольные именованные аргументы для настройки. Для декораторов patch() именованные аргументы передаются в конструктор создаваемого мока. Именованные аргументы предназначены для настройки атрибутов мока:
>>> m = MagicMock(attribute=3, other='fish') >>> m.attribute 3 >>> m.other 'fish'
Возвращаемое значение и побочное действие дочерних моков можно настроить аналогичным образом, используя точечную нотацию. Поскольку вы не можете использовать точечные имена непосредственно в вызове, вам нужно создать словарь и распаковать его с помощью **:
>>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError}
>>> mock = Mock(some_attribute='eggs', **attrs)
>>> mock.some_attribute
'eggs'
>>> mock.method()
3
>>> mock.other()
Traceback (most recent call last):
...
KeyError
Вызываемый мок, который был создан со спецификацией (или спецификацией набора), будет анализировать сигнатуру объекта спецификации при сопоставлении вызовов моку. Таким образом, он может сопоставлять фактические аргументы вызова независимо от того, были ли они переданы позиционно или по имени:
>>> def f(a, b, c): pass ... >>> mock = Mock(spec=f) >>> mock(1, 2, c=3) <Mock name='mock()' id='140161580456576'> >>> mock.assert_called_with(1, 2, 3) >>> mock.assert_called_with(a=1, b=2, c=3)
Это относится к assert_called_with(), assert_called_once_with(), assert_has_calls() и assert_any_call(). При использовании автоспецификации это также будет применяться к вызовам методов объекта-мока.
Изменено в версии 3.4: Добавлена интроспекция сигнатуры для специфицированных и автоспецифицированных объектов-моков.
-
class unittest.mock.PropertyMock(*args, **kwargs) -
Мок, предназначенный для использования в качестве свойства или другого дескриптора в классе.
PropertyMockпредоставляет методы__get__()и__set__(), позволяющие указать возвращаемое значение при обращении к нему.Получение экземпляра
PropertyMockиз объекта вызывает мок без аргументов. Установка вызывает мок с задаваемым значением.>>> class Foo: ... @property ... def foo(self): ... return 'something' ... @foo.setter ... def foo(self, value): ... pass ... >>> with patch('__main__.Foo.foo', new_callable=PropertyMock) as mock_foo: ... mock_foo.return_value = 'mockity-mock' ... this_foo = Foo() ... print(this_foo.foo) ... this_foo.foo = 6 ... mockity-mock >>> mock_foo.mock_calls [call(), call(6)]
Из-за способа хранения атрибутов моков, вы не можете напрямую прикрепить PropertyMock к объекту мока. Вместо этого вы можете прикрепить его к объекту типа мока:
>>> m = MagicMock() >>> p = PropertyMock(return_value=3) >>> type(m).foo = p >>> m.foo 3 >>> p.assert_called_once_with()
Вызов
Объекты моков вызываемы. Вызов вернёт значение, установленное как атрибут 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
Поскольку «имя» является аргументом конструктора Mock, если вы хотите, чтобы ваш объект мока имел атрибут «имя», вы не можете просто передать его при создании. Есть два варианта. Один вариант — использовать configure_mock():
>>> mock = MagicMock() >>> mock.configure_mock(name='my_name') >>> mock.name 'my_name'
Более простой вариант — просто установить атрибут «имя» после создания мока:
>>> mock = MagicMock() >>> mock.name = "foo"
Присоединение моков в качестве атрибутов
Когда вы прикрепляете мок в качестве атрибута другого мока (или как возвращаемое значение), он становится «потомком» этого мока. Вызовы к потомку записываются в атрибутах method_calls и mock_calls родителя. Это полезно для настройки дочерних моков и их последующего присоединения к родителю, или для присоединения моков к родителю, который записывает все вызовы к дочерним элементам и позволяет делать утверждения о порядке вызовов между моками:
>>> parent = MagicMock() >>> child1 = MagicMock(return_value=None) >>> child2 = MagicMock(return_value=None) >>> parent.child1 = child1 >>> parent.child2 = child2 >>> child1(1) >>> child2(2) >>> parent.mock_calls [call.child1(1), call.child2(2)]
Исключением из этого правила является наличие имени у мока. Это позволяет предотвратить «родительские отношения», если по какой-либо причине вы этого не хотите.
>>> mock = MagicMock() >>> not_a_child = MagicMock(name='not-a-child') >>> mock.attribute = not_a_child >>> mock.attribute() <MagicMock name='not-a-child()' id='...'> >>> mock.mock_calls []
Моки, созданные для вас функцией patch(), автоматически получают имена. Чтобы присоединить моки с именами к родителю, используйте метод attach_mock():
>>> thing1 = object()
>>> thing2 = object()
>>> parent = MagicMock()
>>> with patch('__main__.thing1', return_value=None) as child1:
... with patch('__main__.thing2', return_value=None) as child2:
... parent.attach_mock(child1, 'child1')
... parent.attach_mock(child2, 'child2')
... child1('one')
... child2('two')
...
>>> parent.mock_calls
[call.child1('one'), call.child2('two')]
-
1 -
Исключения составляют магические методы и атрибуты (те, что имеют двойные подчёркивания в начале и конце). Mock не создаёт их, а вместо этого вызывает
AttributeError. Это происходит потому, что интерпретатор часто неявно запрашивает эти методы и испытывает значительные трудности, получая новый объект Mock, когда ожидается магический метод. Если вам нужна поддержка магических методов, см. магические методы.
Модули замены
Декораторы patch используются для замены объектов только в области действия функции, которую они декорируют. Они автоматически обрабатывают отмену замены, даже если возникают исключения. Все эти функции также могут использоваться в операторе with или в качестве декораторов классов.
patch
Примечание
patch() прост в использовании. Ключ — выполнение замены в правильном пространстве имён. См. раздел где производить замену.
-
unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs) -
patch()выполняет роль декоратора функции, декоратора класса или менеджера контекста. Внутри тела функции или оператора with, target заменяется на new объект. При выходе из функции/оператора with замена отменяется.Если new опущено, то target заменяется на
MagicMock. Еслиpatch()используется как декоратор и new опущено, созданный mock передаётся как дополнительный аргумент в декорированную функцию. Еслиpatch()используется как менеджер контекста, созданный mock возвращается менеджером контекста.target должен быть строкой в формате
'package.module.ClassName'. target импортируется, и указанный объект заменяется на new объект, поэтому target должен быть импортируемым из среды, откуда вызываетсяpatch(). target импортируется при выполнении декорированной функции, а не во время декорирования.Ключевые аргументы spec и spec_set передаются в
MagicMock, если patch создаёт его для вас.Кроме того, вы можете передать
spec=Trueилиspec_set=True, что заставляет patch передавать мокаемый объект как объект spec/spec_set.new_callable позволяет указать другой класс или вызываемый объект, который будет вызван для создания new объекта. По умолчанию используется
MagicMock.Более мощная форма spec — это autospec. Если вы установите
autospec=True, то mock будет создан со spec, взятым из замещаемого объекта. Все атрибуты mock также будут иметь spec соответствующего атрибута замещаемого объекта. Методы и функции, которые мокаются, будут проверять свои аргументы и будут подниматьTypeError, если они вызваны с неправильной сигнатурой. Для mocks, заменяющих класс, их возвращаемое значение («экземпляр») будет иметь тот же 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()принимает произвольные ключевые аргументы. Они будут переданы вMock(или new_callable) при создании.patch.dict(...),patch.multiple(...)иpatch.object(...)доступны для альтернативных вариантов использования.
patch() в качестве декоратора функции, создавая mock для вас и передавая его в декорированную функцию:
>>> @patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
... print(mock_class is SomeClass)
...
>>> function(None)
True
Замена класса подменяет класс объектом MagicMock экземпляра. Если класс создаётся в тестируемом коде, то это будет return_value mock, который будет использоваться.
Если класс создаётся несколько раз, вы можете использовать side_effect для возвращения нового mock каждый раз. В качестве альтернативы вы можете установить return_value на любое значение, которое вам нужно.
Для настройки значений возврата методов экземпляров в заменённом классе вы должны сделать это в return_value. Например:
>>> class Class:
... def method(self):
... pass
...
>>> with patch('__main__.Class') as MockClass:
... instance = MockClass.return_value
... instance.method.return_value = 'foo'
... assert Class() is instance
... assert Class().method() == 'foo'
...
Если вы используете spec или spec_set и patch() заменяет класс, то значение возврата созданного mock будет иметь тот же 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'
но добавление 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) -
Заменяет указанный член (attribute) на объекте (target) на объект mock.
patch.object()может использоваться как декоратор, декоратор класса или менеджер контекста. Аргументы new, spec, create, spec_set, autospec и new_callable имеют то же значение, что и дляpatch(). Как иpatch(),patch.object()принимает произвольные ключевые аргументы для настройки объекта mock, который он создаёт.При использовании в качестве декоратора класса
patch.object()учитываетpatch.TEST_PREFIXдля выбора методов для обертывания.
Вы можете вызвать patch.object() с тремя аргументами или двумя аргументами. Формат с тремя аргументами принимает объект для замены, имя атрибута и объект для замены атрибута.
При вызове с двумя аргументами вы пропускаете объект замены, и для вас создаётся mock, который передаётся как дополнительный аргумент в декорированную функцию:
>>> @patch.object(SomeClass, 'class_method') ... def test(mock_method): ... SomeClass.class_method(3) ... mock_method.assert_called_with(3) ... >>> test()
spec, create и другие аргументы для patch.object() имеют то же значение, что и для patch().
patch.dict
-
patch.dict(in_dict, values=(), clear=False, **kwargs) -
Заменить словарь или похожий на словарь объект и восстановить исходное состояние словаря после завершения теста.
in_dict может быть словарем или контейнером, похожим на словарь. Если это отображение, то оно должно, по крайней мере, поддерживать получение, установление и удаление элементов, а также итерацию по ключам.
in_dict также может быть строкой, указывающей имя словаря, который затем будет получен путём импорта.
values может быть словарем значений для установки в словарь. values также может быть итерируемым объектом пар
(key, value).Если clear равно true, то словарь будет очищен перед установкой новых значений.
patch.dict()также может быть вызван с произвольными именованными аргументами для установки значений в словарь.patch.dict()может использоваться в качестве менеджера контекста, декоратора или декоратора класса. При использовании в качестве декоратора классаpatch.dict()учитываетpatch.TEST_PREFIXдля выбора методов, которые нужно обернуть.
patch.dict() можно использовать для добавления членов в словарь или просто для того, чтобы тест изменил словарь, а затем гарантировать восстановление словаря по завершении теста.
>>> foo = {}
>>> with patch.dict(foo, {'newkey': 'newvalue'}):
... assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == {}
>>> import os
>>> with patch.dict('os.environ', {'newkey': 'newvalue'}):
... print(os.environ['newkey'])
...
newvalue
>>> assert 'newkey' not in os.environ
Именованные аргументы могут использоваться в вызове patch.dict() для установки значений в словарь:
>>> mymodule = MagicMock()
>>> mymodule.function.return_value = 'fish'
>>> with patch.dict('sys.modules', mymodule=mymodule):
... import mymodule
... mymodule.function('some', 'args')
...
'fish'
patch.dict() можно использовать с объектами, подобными словарям, которые на самом деле не являются словарями. По меньшей мере, они должны поддерживать получение, установление, удаление элементов и либо итерацию, либо проверку на членство. Это соответствует магическим методам __getitem__(), __setitem__(), __delitem__() и либо __iter__(), либо __contains__().
>>> class Container:
... def __init__(self):
... self.values = {}
... def __getitem__(self, name):
... return self.values[name]
... def __setitem__(self, name, value):
... self.values[name] = value
... def __delitem__(self, name):
... del self.values[name]
... def __iter__(self):
... return iter(self.values)
...
>>> thing = Container()
>>> thing['one'] = 1
>>> with patch.dict(thing, one=2, two=3):
... assert thing['one'] == 2
... assert thing['two'] == 3
...
>>> assert thing['one'] == 1
>>> assert list(thing) == ['one']
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(TestCase):
... def setUp(self):
... self.patcher1 = patch('package.module.Class1')
... self.patcher2 = patch('package.module.Class2')
... self.MockClass1 = self.patcher1.start()
... self.MockClass2 = self.patcher2.start()
...
... def tearDown(self):
... self.patcher1.stop()
... self.patcher2.stop()
...
... def test_something(self):
... assert package.module.Class1 is self.MockClass1
... assert package.module.Class2 is self.MockClass2
...
>>> MyTest('test_something').run()
Внимание
Если вы используете эту технику, вы должны убедиться, что замены «отменяются» вызовом stop. Это может быть более сложной задачей, чем кажется, потому что если в методе setUp возникает исключение, то tearDown не вызывается. unittest.TestCase.addCleanup() упрощает эту задачу:
>>> class MyTest(TestCase):
... def setUp(self):
... patcher = patch('package.module.Class')
... self.MockClass = patcher.start()
... self.addCleanup(patcher.stop)
...
... def test_something(self):
... assert package.module.Class is self.MockClass
...
Дополнительное преимущество заключается в том, что вам больше не нужно хранить ссылку на объект patcher.
Также возможно остановить все начатые замены с помощью patch.stopall().
-
patch.stopall() -
Остановить все активные замены. Останавливает только замены, начатые с помощью
start.
замена встроенных функций
Вы можете заменить любые встроенные функции в модуле. Следующий пример заменяет встроенную функцию 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__ -
__floor__,__trunc__и__ceil__ - Сравнения:
__lt__,__gt__,__le__,__ge__,__eq__и__ne__ - Методы контейнеров:
__getitem__,__setitem__,__delitem__,__contains__,__len__,__iter__,__reversed__и__missing__ - Менеджер контекста:
__enter__и__exit__ - Унарные числовые методы:
__neg__,__pos__и__invert__ - Числовые методы (включая варианты с правой частью и на месте):
__add__,__sub__,__mul__,__matmul__,__div__,__truediv__,__floordiv__,__mod__,__divmod__,__lshift__,__rshift__,__and__,__xor__,__or__, и__pow__ - Методы преобразования чисел:
__complex__,__int__,__float__и__index__ - Методы дескрипторов:
__get__,__set__и__delete__ - Сериализация:
__reduce__,__reduce_ex__,__getinitargs__,__getnewargs__,__getstate__и__setstate__
Следующие методы существуют, но не поддерживаются, так как они используются mock, не могут быть установлены динамически или могут вызвать проблемы:
-
__getattr__,__setattr__,__init__и__new__ -
__prepare__,__instancecheck__,__subclasscheck__,__del__
Магическая заглушка
Существует два MagicMock варианта: MagicMock и NonCallableMagicMock.
-
class unittest.mock.MagicMock(*args, **kw) -
MagicMock— это подклассMockс реализациями по умолчанию для большинства магических методов. Вы можете использоватьMagicMockбез необходимости самостоятельной настройки магических методов.Параметры конструктора имеют то же значение, что и для
Mock.Если вы используете аргументы spec или spec_set, то будут созданы только магические методы, присутствующие в спецификации.
-
class unittest.mock.NonCallableMagicMock(*args, **kw) -
Не вызываемый вариант
MagicMock.Параметры конструктора имеют то же значение, что и для
MagicMock, за исключением return_value и side_effect, которые не имеют значения для невызываемой заглушки.
Магические методы устанавливаются с помощью объектов MagicMock, поэтому вы можете настроить и использовать их обычным способом:
>>> mock = MagicMock() >>> mock[3] = 'fish' >>> mock.__setitem__.assert_called_with(3, 'fish') >>> mock.__getitem__.return_value = 'result' >>> mock[2] 'result'
По умолчанию многие методы протокола должны возвращать объекты определённого типа. Эти методы предварительно настроены со значением возврата по умолчанию, чтобы их можно было использовать без ваших действий, если вам не интересно значение возврата. Вы по-прежнему можете изменить значение возврата вручную, если хотите.
Методы и их значения по умолчанию:
-
__lt__:NotImplemented -
__gt__:NotImplemented -
__le__:NotImplemented -
__ge__:NotImplemented -
__int__:1 -
__contains__:False -
__len__:0 -
__iter__:iter([]) -
__exit__:False -
__complex__:1j -
__float__:1.0 -
__bool__:True -
__index__:1 -
__hash__: хэш по умолчанию для заглушки -
__str__: строка по умолчанию для заглушки -
__sizeof__: размер по умолчанию для заглушки
Например:
>>> mock = MagicMock() >>> int(mock) 1 >>> len(mock) 0 >>> list(mock) [] >>> object() in mock False
Два метода сравнения, __eq__() и __ne__(), являются особыми. Они выполняют сравнение по идентичности по умолчанию, используя атрибут side_effect, если вы не измените их возвращаемое значение.
>>> MagicMock() == 3 False >>> MagicMock() != 3 True >>> mock = MagicMock() >>> mock.__eq__.return_value = True >>> mock == 3 True
Возвращаемое значение MagicMock.__iter__() может быть любым итерируемым объектом и не обязательно итератором:
>>> mock = MagicMock() >>> mock.__iter__.return_value = ['a', 'b', 'c'] >>> list(mock) ['a', 'b', 'c'] >>> list(mock) ['a', 'b', 'c']
Если возвращаемое значение является итератором, то итерирование по нему один раз исчерпает его, а последующие итерации приведут к пустому списку:
>>> mock.__iter__.return_value = iter(['a', 'b', 'c']) >>> list(mock) ['a', 'b', 'c'] >>> list(mock) []
MagicMock имеет все поддерживаемые магические методы, кроме некоторых устаревших и малоизвестных.
Магические методы, которые поддерживаются, но не настроены по умолчанию в MagicMock:
__subclasses____dir____format__-
__get__,__set__и__delete__ -
__reversed__и__missing__ -
__reduce__,__reduce_ex__,__getinitargs__,__getnewargs__,__getstate__и__setstate__ -
__getformat__и__setformat__
-
2 -
Магические методы должны ищется в классе, а не в экземпляре. Различные версии Python не согласованы в применении этого правила. Поддерживаемые методы протокола должны работать со всеми поддерживаемыми версиями Python.
-
3 -
Функция в основном подключена к классу, но каждый экземпляр
Mockизолирован от других.
Вспомогательные функции
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 >>> sentinel.some_object 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
>>> args, kwargs = kall
>>> args
(1, 2, 3)
>>> kwargs
{'arg2': 'two', 'arg': 'one'}
>>> args is kall[0]
True
>>> kwargs is kall[1]
True
>>> m = MagicMock()
>>> m.foo(4, 5, 6, arg='two', arg2='three')
<MagicMock name='mock.foo()' id='...'>
>>> kall = m.mock_calls[0]
>>> name, args, kwargs = kall
>>> name
'foo'
>>> args
(4, 5, 6)
>>> kwargs
{'arg2': 'three', 'arg': 'two'}
>>> name is m.mock_calls[0][0]
True
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().
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_once_with', 'assert_called_with', 'assert_has_calls', 'attach_mock', ... >>> from urllib import request >>> dir(Mock(spec=request)) ['AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'AbstractHTTPHandler', 'BaseHandler', ...
Многие бесполезные (приватные для Mock, а не для имитируемого объекта) атрибуты, начинающиеся с подчёркивания и двойного подчёркивания, были отфильтрованы из результата вызова dir() на объекте Mock. Если вам не нравится такое поведение, вы можете отключить его, установив переменную модуля FILTER_DIR:
>>> from unittest import mock >>> mock.FILTER_DIR = False >>> dir(mock.Mock()) ['_NonCallableMock__get_return_value', '_NonCallableMock__get_side_effect', '_NonCallableMock__return_value_doc', '_NonCallableMock__set_return_value', '_NonCallableMock__set_side_effect', '__call__', '__class__', ...
В качестве альтернативы, вы можете просто использовать vars(my_mock) (члены экземпляра) и dir(type(my_mock)) (члены типа), чтобы обойти фильтрацию независимо от mock.FILTER_DIR.
mock_open
-
unittest.mock.mock_open(mock=None, read_data=None) -
Вспомогательная функция для создания мока, заменяющего использование
open(). Она работает дляopen(), вызываемого непосредственно или используемого как менеджер контекста.Аргумент mock — это объект-мока, который нужно настроить. Если
None(по умолчанию), то для вас будет созданMagicMockс ограниченным API методами или атрибутами, доступными в стандартных файловых потоках.read_data — строка для
read(),readline()иreadlines()методов файлового потока для возврата. Вызовы этих методов будут брать данные из read_data до его исчерпания. Мок этих методов довольно упрощенный: каждый раз, когда вызывается mock, read_data перематывается в начало. Если вам нужен больший контроль над данными, которые вы подаете в тестируемый код, вам нужно будет настроить этот мок самостоятельно. Когда этого недостаточно, пакеты in-memory файловой системы на PyPI могут предложить реалистичную файловую систему для тестирования.Изменено в версии 3.4: Добавлена поддержка
readline()иreadlines(). Мокread()был изменён, чтобы потреблять read_data, а не возвращать его при каждом вызове.Изменено в версии 3.5: read_data теперь сбрасывается при каждом вызове mock.
Изменено в версии 3.7.1: Добавлена
__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)
Ваши тесты могут бесшумно и неправильно пройти из-за опечатки.
Вторая проблема носит более общий характер для моделирования. Если вы переделаете часть своего кода, переименуете члены и так далее, любые тесты кода, который все еще использует старый API, но использует моки вместо реальных объектов, все равно пройдут. Это означает, что ваши тесты могут пройти, даже если ваш код сломан.
Обратите внимание, что это еще одна причина, по которой вам нужны интеграционные тесты наряду с модульными. Тестирование всего по отдельности — хорошо, но если вы не тестируете, как ваши модули «связаны вместе», есть много места для ошибок, которые могли бы найти тесты.
mock уже предоставляет функцию для решения этой проблемы, называемую спецификацией. Если вы используете класс или экземпляр в качестве spec для мока, то вы можете обращаться только к атрибутам мока, которые существуют в реальном классе:
>>> from urllib import request >>> mock = Mock(spec=request.Request) >>> mock.assret_called_with Traceback (most recent call last): ... AttributeError: Mock object has no attribute 'assret_called_with'
Спецификация применяется только к самому моку, поэтому у нас все еще есть та же проблема с любыми методами мока:
>>> mock.has_data() <mock.Mock object at 0x...> >>> mock.has_data.assret_called_with()
Автоспецификация решает эту проблему. Вы можете либо передать autospec=True в patch() / patch.object(), либо использовать функцию create_autospec() для создания мока со спецификацией. Если вы используете аргумент autospec=True для patch(), то объект, который заменяется, будет использоваться как объект спецификации. Поскольку спецификация выполняется «лениво» (спецификация создаётся по мере доступа к атрибутам мока), вы можете использовать её с очень сложными или глубоко вложенными объектами (например, модулями, которые импортируют модули, которые импортируют модули), не сильно снижая производительность.
Вот пример его использования:
>>> from urllib import request
>>> patcher = patch('__main__.request', autospec=True)
>>> mock_request = patcher.start()
>>> request is mock_request
True
>>> mock_request.Request
<MagicMock name='request.Request' spec='Request' id='...'>
Вы можете видеть, что request.Request имеет спецификацию. request.Request принимает два аргумента в конструкторе (один из которых — self). Вот что происходит, если мы попытаемся вызвать его неправильно:
>>> req = request.Request() Traceback (most recent call last): ... TypeError: <lambda>() takes at least 2 arguments (1 given)
Спецификация также применяется к экземплярам классов (то есть к значению возврата специфицированных моков):
>>> req = request.Request('foo')
>>> req
<NonCallableMagicMock name='request.Request()' spec='Request' id='...'>
Объекты Request не вызываемы, поэтому возвращаемое значение создания нашего смоделированного request.Request — это невызываемый мок. С установленной спецификацией любые опечатки в наших проверках приведут к правильному ошибке:
>>> req.add_header('spam', 'eggs')
<MagicMock name='request.Request().add_header()' id='...'>
>>> req.add_header.assret_called_with
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'assret_called_with'
>>> req.add_header.assert_called_with('spam', 'eggs')
Во многих случаях вам просто нужно будет добавить autospec=True к вашим существующим вызовам patch(), чтобы защититься от ошибок из-за опечаток и изменений API.
Помимо использования autospec через patch(), существует create_autospec() для непосредственного создания смоделированных моков:
>>> from urllib import request
>>> mock_request = create_autospec(request)
>>> mock_request.Request('foo', 'bar')
<NonCallableMagicMock name='mock.Request()' spec='Request' id='...'>
Однако это не лишено недостатков и ограничений, поэтому это не стандартное поведение. Для того чтобы узнать, какие атрибуты доступны в объекте спецификации, автоспецификация должна проанализировать (получить доступ к атрибутам) спецификацию. По мере прохождения по атрибутам мока происходит соответствующее прохождение по исходному объекту в скрытом режиме. Если какие-либо ваши смоделированные объекты имеют свойства или дескрипторы, которые могут вызывать выполнение кода, то вы можете не иметь возможности использовать автоспецификацию. С другой стороны, гораздо лучше проектировать ваши объекты таким образом, чтобы интроспекция была безопасна 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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/unittest.mock.html