2017-02-22 4 views
1

Я изучаю питонDependency Injection в модульном тесте с Python

Я интересно, если есть механизм «впрыснуть» объект (подделка объекта в моем случае) в класс испытываемого без явного добавления его в организаторе затрат/сеттерах.

## source file 
class MyBusinessClass(): 
    def __init__(self): 
     self.__engine = RepperEngine() 

    def doSomething(self): 
     ## bla bla ... 
     success 

## test file 
## fake I'd like to inkject 
class MyBusinessClassFake(): 
    def __init__(self): 
     pass 

def myPrint(self) : 
    print ("Hello from Mock !!!!") 

class Test(unittest.TestCase): 

    ## is there an automatic mechanism to inject MyBusinessClassFake 
    ## into MyBusinessClass without costructor/setter? 
    def test_XXXXX_whenYYYYYY(self): 

     unit = MyBusinessClass() 
     unit.doSomething() 
     self.assertTrue(.....) 

В моем тесте я хотел бы «впрыснуть» объект «двигатель», не передавая его в блок-схему. Я пробовал несколько вариантов (например: @patch ...) без успеха.

ответ

4

IOC не требуется в Python. Вот питонический подход.

class MyBusinessClass(object): 
    def __init__(self, engine=None): 
     self._engine = engine or RepperEngine() 
     # Note: _engine doesn't exist until constructor is called. 

    def doSomething(self): 
     ## bla bla ... 
     success 

class Test(unittest.TestCase): 

    def test_XXXXX_whenYYYYYY(self): 
     mock_engine = mock.create_autospec(RepperEngine) 
     unit = MyBusinessClass(mock_engine) 
     unit.doSomething() 
     self.assertTrue(.....) 

Вы также можете гасите класс, чтобы обойти constuctor

class MyBusinessClassFake(MyBusinessClass): 
    def __init__(self): # we bypass super's init here 
     self._engine = None 

Тогда в вашей установке

def setUp(self): 
    self.helper = MyBusinessClassFake() 

Теперь в тесте вы можете использовать менеджер контекста.

def test_XXXXX_whenYYYYYY(self): 
    with mock.patch.object(self.helper, '_engine', autospec=True) as mock_eng: 
    ... 

Если вы хотите вывести его с помощью constuctor, вы можете добавить его как атрибут класса.

class MyBusinessClass(): 
    _engine = None 
    def __init__(self): 
     self._engine = RepperEngine() 

Теперь заглушки обойти __init__:

class MyBusinessClassFake(MyBusinessClass): 
    def __init__(self): 
     pass 

Теперь вы можете просто присвоить значение:

unit = MyBusinessClassFake() 
unit._engine = mock.create_autospec(RepperEngine) 
+0

+1. Просто следите за значениями false, когда делаете 'self._engine = engine или Something()', предпочитаете проверять None. Я включил такую ​​функциональность в очень прямолинейный @ инжекторный декоратор для Python 3 ради чистого кода: https://github.com/allrod5/injectable –

0

Ваш вопрос кажется несколько неясным, но нет ничего, что мешало бы вам использовать наследование класса для переопределения исходных методов. В этом случае класс производных будет выглядеть примерно так:

class MyBusinessClassFake(MyBusinessClass): 
    def __init__(self): 
     pass 
+0

«Я бы хотел« ввести »объект« движок », не передавая его в блок-схему». Я не понял, что не ясно? в других языковых модулях (например, java) есть возможность вводить объект в другой объект без сеттера или конструктора затрат. это все – Kasper

+0

Непонятно, что вы одновременно спросите, как вводить оба «MyBusinessClassFake» и «engine» таким образом, чтобы не включать в себя использование патчей, наследования или передачи каких-либо значений, а также автоматическое. Если @patch не может быть и речи, вы уверены, что не можете просто сделать следующее? единица = MyBusinessClass() единица .__ двигатель = RepperEngine() –

+0

извините, что есть недоразумение. Где я сказал, что я не хочу использовать @patch? патч хорош для меня ... Я только что сказал, что не смог заставить его работать с @patch .... – Kasper

0

@ ответ Дэна очень через, но только, чтобы добавить к обсуждению:

Я большой поклонник декораторов, чтобы удалить шаблонный код из сферы действия.

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

Говорит, что я поддерживать модуль для обработки, что: Injectable, которая обеспечивает @autowired декоратор для Python 3, чтобы обеспечить легкие и чистые инъекции зависимостей:

  • функция не должна быть в курсе автоматического связывания на всех
  • зависимостей может быть ленивым инициализируется
  • абонент может явно передавать экземпляры зависимостей при желании

Весь смысл декоратора является очередь кода, как этот:

def __init__(self, *, model: Model = None, service: Service = None): 
    if model is None: 
     model = Model() 

    if service is None: 
     service = Service() 

    self.model = model 
    self.service = service 
    # actual code 

в этот:

@autowired 
def __init__(self, *, model: Model, service: Service): 
    self.model = model 
    self.service = service 
    # actual code 

Нет сложный материал, не требует настройки, никаких рабочих процессов не соблюдается. И теперь ваш функциональный код больше не загромождается кодом инициализации зависимостей.

Декораторный подход очень минималистский. Возможно, что полноценная структура подходит вам лучше. Для этого есть отличные модули, такие как Injector.

Смежные вопросы