2017-02-22 3 views
1

Ich lerne PythonInjection Abhängigkeit in Unit-Test mit Python

Ich frage mich, ob es ein Mechanismus gibt, der „zu injizieren“ ein Objekt (ein gefälschtes Objekt in meinem Fall) in die Klasse im Test ohne es explizit zu hinzugefügt im Costructor/Setter.

In meinem Test möchte ich das Objekt "Engine" "injizieren", ohne es in der Costructor übergeben. Ich habe einige Optionen (z. B. @patch ...) ohne Erfolg ausprobiert.

Antwort

4

IOC wird in Python nicht benötigt. Hier ist ein pythonischer Ansatz.

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(.....) 

Sie könnten auch die Klasse Stub den Konstruktor

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

Dann in Ihrem Setup

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

Jetzt in Ihrem Test umgehen können Sie einen Kontext-Manager verwenden.

Wenn Sie es injizieren möchten, ohne den Konstruktor zu verwenden, können Sie es als Klassenattribut hinzufügen.

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

Jetzt Stummel __init__ zu umgehen:

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

Jetzt können Sie einfach den Wert zuweisen:

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

+1. Achten Sie nur auf falsche Werte, wenn Sie 'self._engine = engine oder Something()' ausführen, und prüfen Sie lieber auf None. Ich habe solche Funktionalität in einen sehr direkten @ injectable Decorator für Python 3 eingepackt, um saubereren Code zu erhalten: https://github.com/allrod5/injectable –

0

Ihre Frage scheint etwas unklar, aber nichts hindert Sie daran, die Klassenvererbung zu verwenden, um die ursprünglichen Methoden zu überschreiben. In diesem Fall würde die Derivat-Klasse einfach so aussehen:

+0

"Ich möchte das Objekt" Engine "" injizieren ", ohne es in der Costructor übergeben". Ich habe nicht verstanden, was ist nicht klar? andere Unit-Test-Frameworks in anderen Sprachen (z. B. Java) haben die Möglichkeit, Objekte in andere Objekte ohne Setter oder Costructor zu injizieren. Das ist alles – Kasper

+0

Es ist unklar, dass Sie gleichzeitig nach einer Möglichkeit fragen, sowohl "MyBusinessClassFake" als auch "Engine" auf eine Weise zu injizieren, die kein Affe-Patchen, Vererben oder Übergeben irgendwelcher Werte erfordert und das auch automatisch sein muss. Wenn @patch nicht in Frage kommt, sind Sie sicher, dass Sie nicht nur Folgendes tun können? Einheit = MyBusinessClass() Einheit .__ Engine = RepperEngine() –

+0

Entschuldigung, es gibt ein Missverständnis. Wo habe ich gesagt, dass ich @patch nicht benutzen möchte? Patch ist gut zu mir ... Ich habe gerade gesagt, dass ich es nicht mit @patch arbeiten konnte .... – Kasper

0

@ Dan Antwort ist sehr durch, sondern nur auf die Diskussion hinzuzufügen:

Ich bin ein großer Fan von Dekorateuren, um Boilerplate Code aus Funktionen zu entfernen.

Während es sehr einfach ist, die Abhängigkeit als einen anderen Parameter zu nehmen, fügt sie irrelevanten Code hinzu, um zu garantieren, dass alle Abhängigkeiten in den meisten Kontexten initialisiert werden.

sagte, dass ich ein Modul behaupten, dass zu handhaben: Injectable, die 3 eine @autowired Dekorateur für Python bietet Injektion einfache und saubere Abhängigkeit zu aktivieren:

  • die Funktion muss nicht der autowiring bewusst sein, auf allen
  • Abhängigkeiten können faul sein
  • initialisiert der Anrufer in der Lage, die Abhängigkeits Instanzen explizit übergeben, wenn
  • gewünscht

Der ganze Sinn des Dekorateur ist wiederum Code wie diese:

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 

in diese:

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

Keine komplexen Sachen, kein Setup, keine Workflows erzwungen. Und jetzt ist Ihr Funktionscode nicht mehr mit Abhängigkeits-Initialisierungscode überladen.

Der Decorator Ansatz ist sehr minimalistisch. Es kann sein, dass ein vollwertiger Rahmen besser zu Ihnen passt. Dafür gibt es hervorragende Module wie Injector.

Verwandte Themen