2016-03-20 3 views
1

-Code von Spring in Aktion:Unit-Tests ohne Dependency Injection

public class DamselRescuingKnight implements Knight { 
    private RescueDamselQuest quest; 
    public DamselRescuingKnight() { 
     this.quest = new RescueDamselQuest(); 
    } 
    public void embarkOnQuest() { 
     quest.embark(); 
    } 
} 

public class BraveKnight implements Knight { 
    private Quest quest; 
    public BraveKnight(Quest quest) { 
     this.quest = quest; 
    } 
    public void embarkOnQuest() { 
     quest.embark(); 
    } 
} 


public class BraveKnightTest { 
    @Test 
    public void knightShouldEmbarkOnQuest() { 
     Quest mockQuest = mock(Quest.class); 
     BraveKnight knight = new BraveKnight(mockQuest); 
     knight.embarkOnQuest(); 
     verify(mockQuest, times(1)).embark(); 
    } 
} 

verstehe ich die Verwendung von Dependency Injection, die uns Implementierung wechseln können, ohne dass die je Code zu modifizieren.

Das Buch sagt "schrecklich schwierig, einen Unit Test zu schreiben ...".

Allerdings kann ich nicht verstehen, wie es sehr schwierig für Unit-Tests ohne Abhängigkeits-Injektion wird! Meine Intuition weigert sich zu kooperieren!

Können Sie anfangen, Junit-/Unit-Tests für die Klasse "DamselRescuingKnight" und für jede andere bessere Beispielklasse (ohne DI) zu schreiben, um mir den Punkt/die Stufe zu verdeutlichen, an dem DI Komponententests vereinfacht?

+1

Ihr Code ist sehr einfach, so dass es schwer zu erklären ist. Wenn quest.embark eine komplizierte Menge an Code ist, in eine Datenbank geht, die Benutzereingaben beinhaltet, und mehrere Computer, dann möchten Sie den komplizierten Code durch etwas einfacheres ersetzen. Aber in Ihrem Code ist es sinnlos, sogar Unit-Test. quest.embark muss getestet werden, aber der Code, den du hier hast, funktioniert nicht. –

Antwort

1

Das Problem ist natürlich die quest Variable. Sie möchten irgendwie überprüfen, dass die embark() Methode aufgerufen wird. Ohne es durch eine verspottete Instanz ersetzen zu können, ist dies sehr schwierig.

Wenn die Variable protected anstelle von private wäre, könnte der Testfall sie überschreiben, weil sie im selben Paket lebt.

Sie können auch die aspektorientierte Programmierung verwenden, um die Variable zu ersetzen.

Aber die einfachste ist, wenn der Code mit Abhängigkeitsinjektion von Anfang an geschrieben wird.

Sie fragen, wie AOP verwendet werden kann. Das Folgende ist ein Beispiel für einen AspectJ-Pointcut, den Sie in einem Komponententest verwenden können, um die RescueDamselQuest -Instanz durch eine verspottete Instanz namens MockRescueDamselQuest zu ersetzen (Entschuldigung, wenn ich die Syntax nicht genau richtig verstanden habe, ist es eine Weile her, dass ich AspectJ benutzt habe)):

aspect MockRescueDamselQuestInstantiations { 
    RescueDamselQuest around(): call(RescueDamselQuest.new()) { 
     return new MockRescueDamselQuest(); 
    } 
} 

Dies wird stattdessen alle Instanziierungen RescueDamselQuest (dh Anrufe zu new RescueDamselQuest()) und zurück ein MockRescueDamselQuest Objekt fangen.

Angesichts der Tatsache, wie viel mehr Verkabelung dies erfordert, würde ich dringend vorschlagen Abhängigkeit Injektion statt!

+0

Die einzige vernünftige Sache, die zu testen ist, ist zu überprüfen, ob 'quest.embark()' aufgerufen wird, wenn 'begehungOnQuest()' aufgerufen wird, in diesem Szenario wie von @James gezeigt. Du sagst es ist sehr schwierig ohne verspottete Instanz. Kannst du es bitte versuchen und zeigen, dass es schwer ist? Es wird mir eine Perspektive geben. Wenn ich DI irgendwie bewusst vermeide, sagt man, dass es von AOP gemacht werden kann. Können Sie bitte auf ein solches Beispiel hinweisen? – user104309

3

Die Schwierigkeit in Ihrem obigen Beispiel kommt, wenn Sie versuchen, DamselRescuingKnight zu testen. Nehmen Sie an, Sie wollen, dass man testen (siehe unten)

public class DamselRescuingKnight implements Knight { 
    private RescueDamselQuest quest; 
    public DamselRescuingKnight() { 
     this.quest = new RescueDamselQuest(); 
    } 
    public void embarkOnQuest() { 
     quest.embark(); 
    } 
} 


public class DamselRescuingKnight Test { 
    @Test 
    public void knightShouldEmbarkOnQuest() { 
     DamselRescuingKnight knight = new DamselRescuingKnight(); 
     knight.embarkOnQuest(); 
     // now what?    
    } 
} 

wie kann man sicher sein, dass knight.embarkOnQuest() nicht wirklich etwas tun? Die Antwort ist, dass du das nicht kannst, weil du nicht auf die intern verwendete Quest-Instanz zugreifen kannst.

Jetzt, um eine solche Klasse testen zu können, fügen Sie dem Ritter eine getQuest() Methode hinzu und fügen dann auch eine isEmbarked() Methode zu Quest hinzu. Es ist auch ziemlich fair zu sagen, dass dieses Beispiel sehr einfach ist, da der Springer nur die Quest ohne Parameter und nichts anderes aufruft. Wenn Ritter mit einer Quest interagieren würde und auch Waffen von einem Schmied erhalten würde, dann müsstest du auch irgendwie Zugang dafür gewähren. Du könntest wahrscheinlich den ganzen Text machen, um das zu erledigen. aber dann gehe davon aus, dass du Parameter an den Schmied weiterleitest - wie stellst du sicher, dass die übergebenen Parameter korrekt sind? Oder wie stellst du sicher, dass der Ritter seine Waffe bekommt vor zur Quest gehen?

Hier kommt die Abhängigkeitsinjektion zum Tragen. Sie können nur Mocks erstellen (entweder indem Sie ein Mock-Framework verwenden oder indem Sie Ihre eigenen Mocks implementieren), so dass Sie überprüfen können, ob Ihr Springer die erwarteten Dinge tut.