Python: отлаживать модуль, не импортируя его или не нуждаясь в нем

Я начинаю использовать библиотеку Python Mock для моего тестирования. Я хочу смоделировать модуль, который импортируется в пространство имен тестируемого модуля, фактически не импортируя его или не требуя, чтобы он существовал первым (т.е. Выдавая ImportError).

Предположим, существует следующий код:

foo.py

 import helpers
 def foo_func():
    return helpers.helper_func()

Цель состоит в том, чтобы протестировать foo_func(), когда 'helpers.py' нигде не существует, и если он существует, действовать так, как будто его нет.

Сначала попробуйте, используя супер классный декоратор @patch:

from mock import patch, sentinel
import foo
@patch("foo.helpers")
def foo_test(mock):
    mock.helper_func.return_value = sentinel.foobar
    assert foo.foo_func() == sentinel.foobar

Это работает, если модуль "помощников" можно импортировать. Если он не существует, я получаю ImportError.

Следующая попытка с патчем без декоратора:

from mock import patch, sentinel, Mock
import foo
helpers_mock = patch("foo.helpers")
helpers_mock.start()

def foo_test():
    helpers_mock.helper_func = Mock('helper_func')
    helpers_mock.helper_func.return_value = sentinel.foobar
    assert foo.foo_func() == sentinel.foobar

Опять же, это не работает, если "помощники" отсутствуют... и, если оно существует, утверждение не выполняется. Не совсем уверен, почему это происходит.

Третья попытка, текущее решение:

import sys
helpers_mock = Mock(name="helpers_mock", spec=['helper_func'])
helpers_mock.__name__ = 'helpers'
sys.modules['helpers'] = helpers_mock
import foo
def foo_test():
    helpers_mock.helper_func.return_value = sentinel.foobar
    assert foo.foo_func() == sentinel.foobar

Этот тест проходит независимо от того, существует ли "helpers.py".

Это лучший способ для достижения этой цели? Предоставляет ли библиотека-издевательство альтернативу этому? Какие еще способы я могу сделать это?

Ответ 1

Вы вроде не понимаете, что такое Mock. Вы должны создавать их, когда хотите объект с определенным интерфейсом, независимо от того, как он реализован.

То, что вы делаете, - это попытка повторной реализации системы модулей python, а также довольно ужасное злоупотребление глобальными переменными для загрузки.

Вместо того, чтобы создавать foo модуль, создайте класс Foo и передайте в помощниках конструктора.

class Foo(object):
    def __init__(self, helpers):
        self.helpers = helpers

# then, instead of import foo:
foo = Foo(mock_helpers)

Даже если настоящие "помощники" на самом деле будут модулем, нет никаких причин, по которым вам нужно возиться с sys.modules и настраивать его с помощью import в ваших тестах.

И если foo должен быть модулем, это тоже хорошо, но вы делаете это так:

# foo.py
class Foo(object):
    pass # same code as before, plus foo_func

try:
   import whatever
   _singleton = Foo(whatever)
except ImportError:
   _singleton = Foo(something_else)

def foo_func():
   return _singleton.foo_func()

Большие куски стандартной библиотеки работают таким образом. Это в значительной степени стандарт для определения одноэлементных модулей.