Python имитирует функцию из импортированного модуля


125

Я хочу понять, как выполнить @patchфункцию из импортированного модуля.

Вот где я пока нахожусь.

Приложение / mocking.py:

from app.my_module import get_user_name

def test_method():
  return get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()

Приложение / my_module / __ init__.py:

def get_user_name():
  return "Unmocked User"

тест / mock-test.py:

import unittest
from app.mocking import test_method 

def mock_get_user():
  return "Mocked This Silly"

@patch('app.my_module.get_user_name')
class MockingTestTestCase(unittest.TestCase):

  def test_mock_stubs(self, mock_method):
    mock_method.return_value = 'Mocked This Silly')
    ret = test_method()
    self.assertEqual(ret, 'Mocked This Silly')

if __name__ == '__main__':
  unittest.main()

Это не работает так, как я ожидал. «Патченный» модуль просто возвращает разблокированное значение get_user_name. Как имитировать методы из других пакетов, которые я импортирую в тестируемое пространство имен?


1
Вопрос в том, чтобы «высмеивать передовой опыт» или есть ли смысл в том, что вы делаете? Что касается первого, я бы сказал использовать имитационную библиотеку, такую ​​как Mock, которая включена в python3.3 + as unittest.mock.
Bakuriu

Я спрашиваю, правильно ли я поступаю. Я посмотрел на Mock, но не вижу способа решить эту конкретную проблему. Есть ли способ воссоздать то, что я сделал выше, в Mock?
nsfyn55

Ответы:


167

Когда вы используете patchдекоратор из unittest.mockпакета, вы не исправляете пространство имен, из которого импортируется модуль (в данном случае app.my_module.get_user_name), вы исправляете его в тестируемом пространстве имен app.mocking.get_user_name.

Чтобы сделать это, Mockпопробуйте что-то вроде следующего:

from mock import patch
from app.mocking import test_method 

class MockingTestTestCase(unittest.TestCase):

    @patch('app.mocking.get_user_name')
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = 'Mocked This Silly'
        ret = test_method()
        self.assertEqual(ret, 'Mocked This Silly')

В документации стандартной библиотеки есть полезный раздел, описывающий это.


это доходит до моей проблемы. get_user_nameнаходится в другом модуле, чем test_method. Есть ли способ поиздеваться над чем-то в sub_module? Я исправил это некрасивым способом ниже.
nsfyn55

6
Не имеет значения, что они get_user_nameнаходятся в другом модуле, чем test_methodпоскольку вы импортируете функцию, app.mockingони находятся в том же пространстве имен.
Матти Джон

2
Откуда взялся test_patch, что это такое?
Mike G

2
test_patch передается декоратором патча и является имитацией объекта get_user_name (то есть экземпляром класса MagicMock). Было бы понятнее, если бы он назывался примерно так get_user_name_patch.
Матти Джон

Как вы ссылаетесь на test_method? Это приведет к ошибке NameError: глобальное имя test_method не определено
Адитья

12

Хотя ответ Матти Джона решает вашу проблему (и помог мне тоже, спасибо!), Я бы, однако, предложил локализовать замену исходной функции get_user_name на поддельную. Это позволит вам контролировать, когда функция заменяется, а когда нет. Кроме того, это позволит вам выполнить несколько замен в одном тесте. Для этого используйте условие 'with' довольно похожим образом:

from mock import patch

class MockingTestTestCase(unittest.TestCase):

    def test_mock_stubs(self):
        with patch('app.mocking.get_user_name', return_value = 'Mocked This Silly'):
            ret = test_method()
            self.assertEqual(ret, 'Mocked This Silly')

6
Это как бы несущественно для поставленного вопроса. Независимо от того, используете ли вы patchв качестве декоратора или диспетчера контекста, зависит от варианта использования. Например, вы можете использовать patchв качестве декоратора для имитации значения для всех тестов в классе xunitили, в pytestто время как в других случаях полезно иметь мелкозернистый контроль, предоставляемый диспетчером контекста.
nsfyn55
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.