Как издеваться над импортом


модуль A включает в себя import B в его верхней части. Однако в условиях тестирования я хотел бы mockB на A (mock A.B) и полностью отказаться от импорта B.

в самом деле B не устанавливается в тестовой среде специально.

a-тестируемый модуль. Мне нужно импортировать все свои функции. B-это модуль, который мне нужно издеваться. Но как я могу издеваться над B внутри A и остановить A от импорта реального B, если первый что делает импорт б?

(причина B не установлена в том, что я использую pypy для быстрого тестирования и, к сожалению, B еще не совместим с pypy.)

как это можно сделать?

6 99

6 ответов:

вы можете назначить 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. Примечание пы не существует, но при запуске test.py ошибка не возвращается и print(A.B.__name__) печать mock_B. Вы все еще должны создать mock_B.py где вы издеваетесь над фактическими функциями / переменными / и т. д. Или вы можете просто назначить макет() непосредственно:

test.py:

import sys
sys.modules['B'] = Mock()
import A

строение __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()

A.a() возвращает b_mock.func() который также может быть высмеян.

b_mock.func.return_value = 'spam'
A.a()  # returns 'spam'

как издеваться над импортом, (mock A. B)?

модуль A включает в себя импорт B в верхней части.

легко, просто издеваться над библиотекой в sys.модули перед импортом:

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)

и затем код занимает некоторое время для запуска, так же, как реальный метод.

Я понимаю, что немного опоздал на вечеринку здесь, но вот несколько безумный способ автоматизировать это с помощью 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)

  1. тут ? Остальное импортируйте. Если еще не ImportError
  2. тут ? Остальное импортируйте. Если все еще нет ImportError
  3. получить атрибут foo на sys.modules['herp.derp']. Иначе ImportError
  4. foo = sys.modules['herp.derp'].foo

есть некоторые недостатки этого взломанного решения: если что-то еще полагается на другие вещи в пути модуля, это своего рода винты. И это только работает для вещей, которые импортируются inline, таких как

def foo():
    import herp.derp

или

def foo():
    __import__('herp.derp')

если вы делаете import ModuleB вы действительно вызываете встроенный метод __import__ как:

ModuleB = __import__('ModuleB', globals(), locals(), [], -1)

вы можете перезаписать этот метод, импортировав __builtin__ модуль и сделать обертку вокруг __builtin__.__import__метод. Или вы могли бы играть с NullImporter крюк от imp модуль. Ловить исключение и издеваться над вашим модулем / классом в except-блок.

указатель на соответствующие документы:

docs.python.org: __import__

доступ к внутренним устройствам импорта с помощью модуля imp

Я надеюсь, что это помогает. Будь очень рекомендуется, чтобы вы вступили в более тайные периметры программирования python и что a) твердое понимание того, чего вы действительно хотите достичь, и b)глубокое понимание последствий важно.

я нашел прекрасный способ издеваться над импортом в Python. Это Заади Эрика решение здесь который я просто использую внутри моего Джанго приложение.

у меня есть класс SeatInterface, который является интерфейсом к Seat модель класса. Так что внутри моего seat_interface модуль у меня такой импорт:

from ..models import Seat

class SeatInterface(object):
    (...)

Я хотел создать изолированные тесты для SeatInterface класса с издевались Seat класс FakeSeat. Проблема была - как ту запустить тесты в автономном режиме, где приложение Django не работает. У меня была ниже ошибка:

неправильно сконфигурировано: запрошена настройка BASE_DIR, но настройки не заданы сконфигурированный. Необходимо либо определить переменную среды DJANGO_SETTINGS_MODULE или настройки вызова.настройка () перед доступом настройки.

пробежал 1 Тест за 0,078 с

ошибка (ошибки=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

а затем тест волшебным образом работает ОК :)

.
Пробежал 1 Тест за 0,002 с

ОК