Как насмехаться с импортом

Модуль 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()