Spec-Zone.ru › Python 3.7

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

END_OF_DOCUMENT_MARKER
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):
  ...
KeyError

configure_mock() упрощает конфигурацию после создания подделки.

__dir__()

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

См. FILTER_DIR для получения информации о том, что делает это фильтрование, и как его отключить.

_get_child_mock(**kw)

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

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

called

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

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

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

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

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

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

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

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

return_value также можно установить в конструкторе:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

call_args_list

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

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

Члены call_args_list — объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.

method_calls

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

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

Члены method_calls — объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.

mock_calls

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

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

Члены mock_calls — объекты call. Их можно распаковать как кортежи, чтобы получить отдельные аргументы. См. вызовы в виде кортежей.

Примечание

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

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

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

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

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

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

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

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

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

Классы Mock поддерживают имитацию магических методов. Подробности см. в разделе магические методы.

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

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

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

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

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

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

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

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

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

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

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

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

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

>>> m = MagicMock()
>>> p = PropertyMock(return_value=3)
>>> type(m).foo = p
>>> m.foo
3
>>> p.assert_called_once_with()

Вызов

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

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

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

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

В этом примере мы модифицируем method для возвращения sentinel.some_object:

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

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

Spec-Zone.ru

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