#python #python-unittest
#python #python-unittest
Вопрос:
Есть ли способ имитировать возврат функции, который вызывается в рамках другого вызова функции? Например:
def bar():
return "baz"
def foo():
return bar()
class Tests(unittest.TestCase):
def test_code(self):
# hijack bar() here to return "bat" instead
assert(foo() == "bat")
Я пробовал использовать @mock.patch
, но обнаружил, что это позволяет мне имитировать только функцию, которую я вызываю, а не функцию, которая будет вызвана в результате вызова другой функции.
Ответ №1:
Универсальный патч
unittest.mock.patch
делает именно то, что мой другой ответ предложил из коробки. Вы можете добавить столько @patch
аннотаций, сколько вам нужно, и выбранные вами объекты будут исправлены:
from unittest.mock import patch
def bar():
return "baz"
def foo():
return bar()
class Tests(unittest.TestCase):
@patch(__name__ '.bar', lambda: 'bat')
def test_code(self):
assert(foo() == "bat")
В этой конфигурации функция bar
будет восстановлена после test_code
завершения. Если вы хотите, чтобы один и тот же патч применялся ко всем тестовым примерам в вашем классе, аннотируйте весь класс:
@patch(__name__ '.bar', lambda: 'bat')
class Tests(unittest.TestCase):
def test_code(self):
assert(foo() == "bat")
Глобальные исправления
Вы также можете unittest.mock.patch.dict
получить тот же результат в своем глобальном пространстве имен:
class Tests(unittest.TestCase):
@patch.dict(globals(), {'bar': lambda: 'bat'})
def test_code(self):
assert(foo() == "bat")
Комментарии:
1. Это здорово. Не представлял, что вы могли бы сделать это с помощью Patch. Спасибо за вашу помощь
Ответ №2:
Вы можете написать диспетчер контекста для временной замены объектов в глобальном пространстве имен:
class Hijack:
def __init__(self, name, replacement, namespace):
self.name = name
self.replacement = replacement
self.namespace = namespace
def __enter__(self):
self.original = self.namespace[self.name]
self.namespace[self.name] = self.replacement
def __exit__(self, *args):
self.namespace[self.name] = self.original
Вы можете вызвать свою фиктивную функцию с помощью взломанного метода:
def bar():
return "baz"
def bar_mock():
return "bat"
def foo():
return bar()
class Tests(unittest.TestCase):
def test_code(self):
with Hijack('bar', bar_mock, globals()):
assert(foo() == "bat")
Это довольно общий подход, который можно использовать вне модульного тестирования. На самом деле, довольно просто обобщить это для работы с любым изменяемым объектом, который может быть представлен в виде некоторого отображения:
class Hijack:
def __init__(self, name, replacement, namespace, getter=None, setter=None):
self.name = name
self.replacement = replacement
self.namespace = namespace
self.getter = type(namespace).__getitem__ if getter is None else getter
self.setter = type(namespace).__setitem__ if setter is None else setter
def __enter__(self):
self.original = self.getter(self.namespace, self.name)
self.setter(self.namespace, self.name, self.replacement)
def __exit__(self, *args):
self.setter(self.namespace, self.name, self.original)
Для классов и других объектов вы бы использовали getter=getattr
и setter=setattr
. Для ситуаций, когда None
предпочтительнее KeyError
, вы можете использовать getter=dict.get
и т.д.
Ответ №3:
Также вы могли бы использовать seattr
метод monkeypatch
fixture pytest. Для первых аргументов он принимает объект, подлежащий исправлению, или строка будет интерпретироваться как путь импорта с пунктиром, причем последняя часть является именем атрибута:
# foo_module.py
def bar():
return "baz"
def foo():
return bar()
# test_foo.py
from foo_module import foo
def test_foo(monkeypatch):
monkeypatch.setattr('foo_module.bar', lambda: 'bat')
assert foo() == "bat"
Комментарии:
1. Интересно, я попробую это. Будет ли проблема придерживаться описанного выше подхода к исправлению, но это полезно знать в любом случае. Спасибо