Mocking функции с использованием python mock
Я пытаюсь Mock функция (которая возвращает некоторый внешний контент), используя модуль python mock (http://www.voidspace.org.uk/python/mock/index.html).
У меня возникают некоторые проблемы с издевательскими функциями, которые импортируются в модуль.
Например, в util.py у меня есть
def get_content():
return "stuff"
Я хочу mock util.get_content, чтобы он возвращал что-то еще.
Я пытаюсь это сделать:
util.get_content=Mock(return_value="mocked stuff")
Если get_content
вызывается внутри другого модуля, на самом деле он действительно не возвращает возвращенный объект. Я что-то пропустил с точки зрения использования Mock?
Обратите внимание, что если я вызываю следующее, все будет работать правильно:
>>> util.get_content=Mock(return_value="mocked stuff")
>>> util.get_content()
"mocked stuff"
Однако, если get_content вызывается из другого модуля, он вызывает исходную функцию вместо измененной версии:
>>> from mymodule import MyObj
>>> util.get_content=Mock(return_value="mocked stuff")
>>> m=MyObj()
>>> m.func()
"stuff"
Содержимое mymodule.py
from util import get_content
class MyObj:
def func():
get_content()
Итак, я догадываюсь, что мой вопрос: как я могу вызвать функцию Mocked из функции внутри модуля, который я вызываю?
Похоже, здесь может быть виноват from module import function
, поскольку он не указывает на функцию Mocked.
Ответы
Ответ 1
Я думаю, что у меня есть обходное решение, хотя я все еще не совсем понимаю, как решить общий случай
В mymodule, если я заменю
from util import get_content
class MyObj:
def func():
get_content()
с
import util
class MyObj:
def func():
util.get_content()
Кажется, что Mock вызывается. Похоже, что пространства имен должны совпадать (что имеет смысл). Однако, странно, что я ожидал бы
import mymodule
mymodule.get_content = mock.Mock(return_value="mocked stuff")
сделать трюк в исходном случае, когда я использую синтаксис from/import (который теперь тянет get_content в mymodule). Но это все еще относится к unmocked get_content.
Оказывает значение пространства имен - просто нужно помнить об этом при написании кода.
Ответ 2
Вам нужно исправить функцию, в которой она используется. В вашем случае это будет в модуле mymodule.
import mymodule
>>> mymodule.get_content = Mock(return_value="mocked stuff")
>>> m = mymodule.MyObj()
>>> m.func()
"mocked stuff"
В документах есть ссылка: http://docs.python.org/dev/library/unittest.mock.html#where-to-patch
Ответ 3
Предположим, вы создаете свой макет внутри модуля foobar
:
import util, mock
util.get_content = mock.Mock(return_value="mocked stuff")
Если вы импортируете mymodule
и вызовите util.get_content
без предварительного импорта foobar
, ваш макет не будет установлен:
import util
def func()
print util.get_content()
func()
"stuff"
Вместо
import util
import foobar # substitutes the mock
def func():
print util.get_content()
func()
"mocked stuff"
Обратите внимание, что foobar
можно импортировать из любого места (модуль A импортирует B, который импортирует foobar), пока foobar
оценивается до вызова util.get_content
.
Ответ 4
Общий случай - использовать patch
из mock
. Рассмотрим следующее:
utils.py
def get_content():
return 'stuff'
mymodule.py
from util import get_content
class MyClass(object):
def func(self):
return get_content()
test.py
import unittest
from mock import patch
from mymodule import MyClass
class Test(unittest.TestCase):
@patch('mymodule.get_content')
def test_func(self, get_content_mock):
get_content_mock.return_value = 'mocked stuff'
my_class = MyClass()
self.assertEqual(my_class.func(), 'mocked stuff')
self.assertEqual(get_content_mock.call_count, 1)
get_content_mock.assert_called_once()
Обратите внимание, что get_content
высмеивается, это не util.get_content
, а mymodule.get_content
, поскольку мы используем его в mymodule
.
Выше было протестировано с mock v2.0.0, nosetests v1.3.7 и python v2.7.9.
Ответ 5
Пока он не дает ответ на ваш вопрос напрямую, другой возможной альтернативой является преобразование вашей функции в статический метод с помощью метода @static.
Итак, вы можете преобразовать свои утилиты модулей в класс, используя что-то вроде:
class util(object):
@staticmethod
def get_content():
return "stuff"
Затем макет исправляет его правильно.