Как насмехаться с импортом
Модуль A
включает import B
в своей верхней части. Однако в тестовых условиях я хотел бы высмеивать B
в A
(mock AB
) и полностью воздерживаться от импорта B
На самом деле, B
не установлен в тестовой среде специально.
A
- это проверяемая единица. Я должен импортировать A
со всеми его функциями. B
- это модуль, который мне нужен для макета. Но как я могу высмеять B
внутри A
и остановить A
от импорта реального B
, если первое, что делает A
, это импорт B
?
(Причина, по которой B не установлен, заключается в том, что я использую pypy для быстрого тестирования, и, к сожалению, B пока не совместим с pypy.)
Как это могло быть сделано?
Ответы
Ответ 1
Вы можете назначить sys.modules['B']
перед импортом A
, чтобы получить то, что вы хотите:
test.py
import sys
sys.modules['B'] = __import__('mock_B')
import A
print(A.B.__name__)
A.py
import B
Примечание. B.py не существует, но при запуске test.py
ошибка не возвращается и print(A.B.__name__)
печатает mock_B
. Вам все равно нужно создать mock_B.py
, где вы издеваетесь над фактическими функциями/переменными B и т.д. Или вы можете просто назначить Mock() напрямую:
test.py
import sys
sys.modules['B'] = Mock()
import A
Ответ 2
Встроенный __import__
можно смоделировать с помощью библиотеки 'mock' для большего контроля:
# Store original __import__
orig_import = __import__
# This will be the B module
b_mock = mock.Mock()
def import_mock(name, *args):
if name == 'B':
return b_mock
return orig_import(name, *args)
with mock.patch('__builtin__.__import__', side_effect=import_mock):
import A
Скажем выглядит следующим образом: A
import B
def a():
return B.func()
Aa()
возвращает b_mock.func()
который также можно b_mock.func()
.
b_mock.func.return_value = 'spam'
A.a() # returns 'spam'
Примечание для Python 3: Как указано в журнале изменений для 3.0, __builtin__
теперь называется builtins
:
Переименован модуль __builtin__
во builtins
(удаление подчеркиваний, добавление и).
Код в этом ответе работает нормально, если вы замените __builtin__
на builtins
для Python 3.
Ответ 3
Как издеваться над импортом (mock A.B)?
Модуль A включает в себя импорт B вверху.
Просто, просто издевайтесь над библиотекой в sys.modules, прежде чем она будет импортирована:
if wrong_platform():
sys.modules['B'] = mock.MagicMock()
а затем, пока A
не полагается на конкретные типы данных, возвращаемых из объектов B:
import A
должен работать.
Вы также можете высмеивать import A.B
:
Это работает, даже если у вас есть подмодули, но вы хотите издеваться над каждым модулем. Скажите, что у вас есть это:
from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink
Чтобы посмеяться, просто выполните ниже, прежде чем импортируется модуль, который содержит выше:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
(Мой опыт: у меня была зависимость, которая работает на одной платформе, Windows, но не работала в Linux, где мы запускаем ежедневные тесты.
Поэтому мне нужно было высмеять зависимость для наших тестов. К счастью, это был черный ящик, поэтому мне не нужно было налаживать много взаимодействия.)
Мокучие побочные эффекты
Приложение: На самом деле мне нужно было симулировать побочный эффект, который занял некоторое время. Поэтому мне понадобился метод объекта, чтобы спать на секунду. Это будет работать следующим образом:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
# setup the side-effect:
from time import sleep
def sleep_one(*args):
sleep(1)
# this gives us the mock objects that will be used
from foo.bar import MyObject
my_instance = MyObject()
# mock the method!
my_instance.method_that_takes_time = mock.MagicMock(side_effect=sleep_one)
И тогда код занимает некоторое время для запуска, как и настоящий метод.
Ответ 4
Я понимаю, что я немного опаздываю на вечеринку здесь, но вот несколько безумный способ автоматизировать это с помощью библиотеки mock
:
(здесь пример использования)
import contextlib
import collections
import mock
import sys
def fake_module(**args):
return (collections.namedtuple('module', args.keys())(**args))
def get_patch_dict(dotted_module_path, module):
patch_dict = {}
module_splits = dotted_module_path.split('.')
# Add our module to the patch dict
patch_dict[dotted_module_path] = module
# We add the rest of the fake modules in backwards
while module_splits:
# This adds the next level up into the patch dict which is a fake
# module that points at the next level down
patch_dict['.'.join(module_splits[:-1])] = fake_module(
**{module_splits[-1]: patch_dict['.'.join(module_splits)]}
)
module_splits = module_splits[:-1]
return patch_dict
with mock.patch.dict(
sys.modules,
get_patch_dict('herp.derp', fake_module(foo='bar'))
):
import herp.derp
# prints bar
print herp.derp.foo
Причина, по которой это настолько смехотворно сложна, заключается в том, что когда происходит импорт, python в основном делает это (например, from herp.derp import foo
)
- Существует ли
sys.modules['herp']
? Иначе импортируйте его. Если все еще не ImportError
- Существует ли
sys.modules['herp.derp']
? Иначе импортируйте его. Если все еще не ImportError
- Получить атрибут
foo
из sys.modules['herp.derp']
. Else ImportError
-
foo = sys.modules['herp.derp'].foo
Есть некоторые недостатки этого взломанного решения: если что-то еще опирается на другие вещи в модуле, этот тип винтов заканчивается. Также это работает только для вещей, которые импортируются в строку, например
def foo():
import herp.derp
или
def foo():
__import__('herp.derp')
Ответ 5
Если вы выполните import ModuleB
, вы действительно вызываете встроенный метод __import__
как:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
Вы можете перезаписать этот метод, импортировав модуль __builtin__
и создав обертку вокруг метода __builtin__.__import__
. Или вы можете играть с крюком NullImporter
из модуля imp
. Захват исключения и отладка вашего модуля/класса в except
-block.
Указатель на соответствующие документы:
docs.python.org: __import__
Доступ к внутренним компонентам импорта с помощью модуля imp
Надеюсь, это поможет. Быть ВЫСОКО советовали, что вы входите в более загадочные периметры программирования на питоне и что a) твердое понимание того, чего вы действительно хотите достичь, и б) глубокое понимание последствий важно.
Ответ 6
Я нашел прекрасный способ издеваться над импортом в Python. Это решение Eric Zaadi найдено здесь, которое я просто использую в своем приложении Django.
У меня есть класс SeatInterface
, который является интерфейсом класса Seat
.
Поэтому внутри моего модуля seat_interface
у меня есть такой импорт:
from ..models import Seat
class SeatInterface(object):
(...)
Я хотел создать изолированные тесты для класса SeatInterface
с классом Seat
как FakeSeat
. Проблема заключалась в том, как tu запускает тесты в автономном режиме, когда приложение Django не работает. У меня была ошибка:
Неправильно Конфигурировано: запрошено установка BASE_DIR, но настройки не сконфигурировано. Вы должны либо определить переменную окружения DJANGO_SETTINGS_MODULE или вызовите settings.configure() перед доступом Настройки.
Ran 1 тест в 0.078s
FAILED (ошибки = 1)
Решение:
import unittest
from mock import MagicMock, patch
class FakeSeat(object):
pass
class TestSeatInterface(unittest.TestCase):
def setUp(self):
models_mock = MagicMock()
models_mock.Seat.return_value = FakeSeat
modules = {'app.app.models': models_mock}
patch.dict('sys.modules', modules).start()
def test1(self):
from app.app.models_interface.seat_interface import SeatInterface
И затем тест волшебным образом выполняется нормально:)
.
Ran 1 тест в 0.002s
OK
Ответ 7
Вы можете использовать patch и MagicMock
#Python3.4
from unittest.mock import patch, MagicMock
#older versions
from mock import patch, MagicMock
import A
mock = MagicMock()
class ATestCase(TestCase):
def setUp():
#function
mock.foo.return_value = 'value'
#variable
mock.bar=1234
@patch('B',mock)
test_a_should_display_info(self):
#code and assert
print(A.B) #<MagicMock id='140047253898912'>
Ответ 8
Ответ аарона Холла работает для меня.
Просто хочу упомянуть одну важную вещь,
если в A.py
вы делаете
from B.C.D import E
затем в test.py
вы должны смоделировать каждый модуль на пути, в противном случае вы получите ImportError
sys.moduels['B'] = mock.MagicMock()
sys.moduels['B.C'] = mock.MagicMock()
sys.moduels['B.C.D'] = mock.MagicMock()