2009-09-23 14 views
6

Ich arbeite mit einem vorhandenen Code und versuche, ihn hinzuzufügen und die Komponententests dafür zu erhöhen. Aber einige Probleme mit dem Test des Codes zu bekommen.Entwerfen von Konstruktoren für die Testbarkeit

Original-Constructor:

public Info() throws Exception 
{ 
    _ServiceProperties = new ServiceProperties(); 
    _SshProperties = new SshProperties(); 
} 

Ich bin mir bewusst, dass dies schlecht ist, und offensichtlich nicht prüfbar. In einer Junit-Umgebung kann diese Klasse nicht jedes Mal erstellt werden, da sie nicht in der Lage ist, die notwendigen Eigenschaften zu finden, um sich selbst zu konstruieren. Nun, ich bin mir bewusst, dass diese Klasse viel mehr testbar wäre mit der einfachen Änderung der Bewegung von allem, was mit "neu" als Parameter vorangestellt ist.

So beende ich oben mit:

New Constructor:

public Info(ServiceProperties srvProps, SshProperties sshProps) throws Exception 
{ 
    _ServiceProperties = srvProps; 
    _SshProperties = sshProps; 
} 

Was mich richtig Unit-Test diese Info-Klasse ermöglicht. Das Problem aber ist jetzt, dass alle Arbeiten in eine andere Klasse geschoben wird:

einige andere Klasse Methode:

public void useInfo() throws Exception 
{ 
    ServiceProperties srvProps = new ServiceProperties(); 
    SshProperties sshProps = new SshProperties(); 
    Info info = new Info(srvProprs, sshProprs); 
    doStuffWithInfo(info); 
} 

Nun ist diese Methode nicht prüfbar ist. Alles, was ich geschafft habe, ist wegzudrücken, wo die Konstruktionen dieser Property-Objekte auftreten, und irgendwo anders wird ein Stück Code stecken bleiben und muss eigentlich "neu" heißen.

Hier ist der Haken für mich: Ich kann nicht herausfinden, wie man diese Kette von Ereignissen bricht, einfach diese "neuen" Anrufe woanders hinzuschieben. Was vermisse ich?

Antwort

7

Verwenden Sie ein Dependency Injection-Framework wie Spring. Diese Anwendung von Inversion of Control bedeutet, dass jeder Ihrer Typen die Fallstricke vermeiden kann, die Sie gesehen haben, und die Konfiguration so belassen, dass Komponenten miteinander "verkabelt" werden.

Diese introduction to Spring (pdf) gibt einen umfassenden Überblick über Spring. Das erste oder zweite Kapitel sollte ausreichen, um die Konzepte zu erklären.

Siehe auch Inversion of Control Containers and the Dependency Injection pattern von Martin Fowler

+0

können Sie auch einen Blick auf unsere Frühjahrskursmaterial: http://www.trainologic.org/courses/info/2 –

0

Die einfachste Antwort ist Frühling. Eine andere Antwort ist, Ihre Konfigurations-Sachen in JNDI zu setzen. Frühling in mancher Hinsicht ist eine bessere Antwort, vor allem, wenn Sie nichts haben, das sich je nach Umgebung ändert.

2

Sie haben die richtige Idee. Vielleicht hilft dir das. Ich empfehle Ihnen, zwei Regeln für alle Ihre Signifikanzklassen zu befolgen, wobei "von Bedeutung" bedeutet, dass es schwieriger ist, die Klasse zu testen, wiederzuverwenden oder zu pflegen, wenn Sie die Schritte nicht befolgen. Hier sind die Regeln:

  1. nie oder instanziiert Selbst erwerben eine Abhängigkeit
  2. immer Programm

Sie haben 1 in der Regel # einen Start-Schnittstellen. Sie haben Ihre Klasse Info so geändert, dass ihre Abhängigkeiten nicht mehr erstellt werden. Mit "Abhängigkeit" meine ich andere Klassen, Konfigurationsdaten, die aus Property-Dateien geladen sind oder was auch immer, usw.Wenn Sie davon abhängen, wie etwas instanziiert wird, binden Sie Ihre Klasse daran und machen es schwieriger zu testen, wiederzuverwenden und zu warten. Also, selbst wenn eine Abhängigkeit über eine Factory oder einen Singleton erstellt wird, lass deine Klasse sie nicht erstellen. Haben Sie etwas anderes rufen create() oder getInstance() oder was auch immer und geben Sie es in.

So wählte Sie die "etwas anderes", um die Klasse, die Ihre Klasse verwendet, und erkannte, dass es einen schlechten Geruch zu sein. Die Abhilfe besteht darin, stattdessen den Eingangspunkt zu Ihrer Anwendung alle Abhängigkeiten instanziieren zu lassen. In einer traditionellen Java-App ist dies Ihre main() Methode. Wenn Sie darüber nachdenken, Klassen zu instanziieren und sie miteinander zu verbinden oder sie miteinander zu "verkabeln", ist eine besondere Art von Logik: "Application Assembly" -Logik. Ist es besser, diese Logik im gesamten Code zu verbreiten oder sie an einem Ort zu sammeln, um sie leichter zu verwalten? Die Antwort ist, dass das Sammeln an einem Ort besser ist - nicht nur für die Wartung, sondern dadurch, dass alle wichtigen Klassen in nützlichere und flexiblere Komponenten umgewandelt werden.

In Ihrem main() oder Äquivalent von main() sollten Sie alle Objekte, die Sie benötigen, erstellen und sie an die Setter und Konstruktoren der anderen Benutzer weitergeben, um sie miteinander zu verbinden. Ihre Komponententests würden sie dann anders verkabeln, indem sie Mock-Objekte oder ähnliche Dinge weitergeben. Die Handlung, dies zu tun, wird "Abhängigkeitsinjektion" genannt. Nachdem Sie getan haben, wie ich sage, werden Sie wahrscheinlich eine große hässliche main() Methode haben. Dies ist, wo ein Dependency-Injection-Tool Ihnen helfen kann und tatsächlich Ihren Code unendlich flexibler machen kann. Das Werkzeug, das ich vorschlagen würde, wenn Sie zu diesem Punkt kommen, wie andere auch vorgeschlagen haben, ist Spring.

Die weniger wichtige Regel # 2 - immer Programm auf Schnittstellen, ist immer noch sehr wichtig, weil es alle Abhängigkeiten von der Implementierung beseitigt, Wiederverwendung viel einfacher zu machen, geschweige Verwendung anderer Tools wie Mock Object Frameworks, ORM-Frameworks usw. einfacher auch.

1

Selbst Abhängigkeitsinjektionsframeworks wie Spring, Guice, PicoContainer usw. benötigen eine Art Boostrap, so dass Sie immer etwas aufbauen müssen.

Ich würde vorschlagen, dass Sie eine Provider/Factory verwenden, die eine konfigurierte Instanz Ihrer Klasse zurückgibt. Dadurch können Sie die "Erstellungs" -Hierarchie verlassen.

0

Sie lassen die etwas andere Klasse zu viel Wissen über die Info-Klasse und ihre Abhängigkeiten haben. Ein Dependency-Injection-Framework würde eine Provider-Klasse verwenden. Mit Hilfe generische Typen ein Anbieter von Info-Objekte machen kann:

interface Provider<T> { 
    T get(); 
} 

Wenn Ihr some-andere-Klasse einen Provider nehmen <Info> in seinem Konstruktor Ihre Methode würde wie folgt aussehen:

public void useInfo() throws Exception 
{ 
    Info info = infoProvider.get(); 
    doStuffWithInfo(info); 
} 

Dies hat Die Konstruktion von konkreten Klassen wurde aus Ihrem Code entfernt. Der nächste Schritt besteht darin, Info zu einer Schnittstelle zu machen, um es einfacher zu machen, einen Mock für den spezifischen Unit-Testfall zu erstellen.

Und ja, das wird drücken und schieben alle Objekt Bau-Code weiter und weiter nach oben. Es wird zu einem Modul führen, das nur beschreibt, wie man Dinge miteinander verbindet. Die "Regeln" für solchen Code sind, dass er frei von bedingten Anweisungen und Schleifen sein sollte.

Ich empfehle das Lesen Misko Heverys blog. Alle Präsentationen sind nützlich und die Anleitung zum Schreiben testbarer Code als kleines Regelwerk ausdrucken, sobald Sie die Regeln verstehen, ist eine gute Sache.

1

Ihre Konstruktoren sind nicht falsch und das Problem ist nicht, wann/wo Code ausgeführt wird, es geht darum, was alle anderen erwähnten: Dependency Injection. Sie müssen Mock-SshProperties-Objekte erstellen, um Ihr Objekt zu instanziieren. Die einfachste Art und Weise (die Klasse unter der Annahme nicht als endgültig markiert) ist die Klasse zu erweitern:

public class MockSshProperties extends SshProperties { 
    // implemented methods 
} 

Sie können Sie Mock Frameworks wie Mockito verwenden:

public class Info { 

    private final sshProps; 
    private final serviceProps; 

    public Info() { 
     this(new SshProperties(), new ServiceProperties()); 
    } 

    public Info(SshProperties arg1, ServiceProperties arg2) { 
     this.sshProps = arg1; 
     this.serviceProps = arg2 
    } 
} 

public class InfoTester 
{ 
    private final static SshProperties sshProps = mock(SshProperties.class); 
    private final static ServiceProperties serviceProps = mock(ServiceProperties.class); 
    static { 
     when(sshProps.someGetMethod("something")).thenReturn("value"); 
    } 

    public static void main(String[] args) { 
     Info info = new Info(sshProps, serviceProps); 
     //do stuff 
    } 
} 
Verwandte Themen