Wie sollten eingestellt Unit-Tests Datenquellen, wenn sie nicht in einem Anwendungsserver ausgeführt wird?

StackOverflow https://stackoverflow.com/questions/831760

Frage

Vielen Dank für Ihre Hilfe. Eine Reihe von euch auf dem Laufenden (wie ich erwartet sollte) Antworten meine ganze Ansatz angibt, war falsch, oder dass Low-Level-Code sollte nie wissen müssen, ob sie in einem Container ausgeführt wird. Ich würde eher zustimmen. Allerdings bin ich mit einem komplexen Legacy-Anwendung zu tun und haben nicht die Möglichkeit, einen großen Refactoring für das aktuelle Problem zu tun.

Lassen Sie mich einen Schritt zurück und stellen die Frage nach der Motivation meiner ursprünglichen Frage.

Ich habe eine Legacy-Anwendung unter JBoss ausgeführt wird, und haben einige Änderungen an untergeordneten Code. Ich habe einen Komponententest für meine Modifikation erstellt. Um den Test auszuführen, muss ich mit einer Datenbank verbinden.

Der Legacy-Code erhält die Datenquelle auf diese Weise:

(jndiName ist eine definierte Zeichenfolge)

Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

Mein Problem ist, dass, wenn ich diesen Code unter Unit-Test ausführen, die Context keine Datenquellen definiert hat. Meine Lösung für dieses Problem war, zu versuchen, um zu sehen, ob ich unter dem Anwendungsserver renne, und wenn nicht, erstellen Sie den Test Datasource und gibt es zurück. Wenn ich unter dem App-Server laufen lasse, dann verwende ich den Code oben.

Also, mein real Frage ist: Was ist der richtige Weg, dies zu tun? Gibt es irgendeine Art und Weise genehmigte der Unit-Test den Kontext einrichten können Sie die entsprechende Datenquelle zurück, so dass der im Test befindlichen Code sein, muss nicht wissen, wo es läuft?


Für Kontext: meine ursprüngliche Frage:

Ich habe einige Java-Code, ob wissen muss oder nicht unter JBoss ausgeführt wird. Gibt es eine kanonische Weise für Code zu sagen, ob es in einem Container ausgeführt wird?

Mein erster Ansatz wurde durch einen Experimentier entwickelt und besteht aus dem Ausgangskontext immer und Prüfung, dass es bestimmte Werte nachschlagen kann.

private boolean isRunningUnderJBoss(Context ctx) {
        boolean runningUnderJBoss = false;
        try {
            // The following invokes a naming exception when not running under
            // JBoss.
            ctx.getNameInNamespace();

            // The URL packages must contain the string "jboss".
            String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs");
            if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) {
                runningUnderJBoss = true;
            }
        } catch (Exception e) {
            // If we get there, we are not under JBoss
            runningUnderJBoss = false;
        }
        return runningUnderJBoss;
    }

Context ctx = new InitialContext();
if (isRunningUnderJboss(ctx)
{
.........

Nun scheint dies zu funktionieren, aber es fühlt sich an wie ein Hack. Was ist der „richtige“ Weg, dies zu tun? Im Idealfall würde ich einen Weg, wie die mit einer Vielzahl von Anwendungsservern funktionieren würde, nicht nur JBoss.

War es hilfreich?

Lösung

Der ganze Ansatz fühlt sich für mich falsch geleitet. Wenn Ihre App muss wissen, welcher Behälter läuft es in Sie etwas falsch machen.

Wenn ich Frühling verwenden kann ich von Tomcat zu WebLogic bewegen und zurück ohne etwas zu ändern. Ich bin sicher, dass mit dem richtigen Konfiguration kann ich auch den gleichen Trick mit JBOSS zu tun. Das ist das Ziel, das ich für schießen würde.

Andere Tipps

Das gesamte Konzept ist von hinten nach vorne. Untere Ebene Code sollte nicht diese Art von Tests tun. Wenn Sie eine andere Implementierung benötigen gibt es an einer entsprechenden Stelle nach unten.

Einige Kombination von Injection-Abhängigkeit (ob durch Frühling, Konfigurationsdateien oder Programmargumente) und Factory-Muster wäre am besten in der Regel arbeiten.

Als Beispiel gebe ich ein Argument an meinen Ant-Skripte, die Setup-Konfigurationsdateien je nachdem, ob das Ohr oder Krieg in eine Entwicklung, Erprobung, oder Produktionsumgebung.

Vielleicht so etwas wie diese (hässlich, aber es kann funktionieren)

 private void isRunningOn( String thatServerName ) { 

     String uniqueClassName = getSpecialClassNameFor( thatServerName );
     try { 
         Class.forName( uniqueClassName );
     } catch ( ClassNotFoudException cnfe ) { 
         return false;
     }
     return true;
 } 

Die getSpecialClassNameFor Methode würde eine Klasse zurück, die für jeden Application Server eindeutig ist (und möglicherweise neue Klassennamen zurück, wenn mehr Apps-Server hinzugefügt werden)

Dann verwenden Sie es mögen:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

Wer baut die Initial? Seine Konstruktion außerhalb des Codes sein müssen, die Sie testen wollen, oder sonst werden Sie nicht den Kontext verspotten können.

Da Sie gesagt, dass Sie auf einer Legacy-Anwendung arbeiten, zunächst den Code Refactoring, so dass Sie leicht Abhängigkeits den Kontext oder die Datenquelle die Klasse injiziert. Dann können Sie noch einfacher schreiben Tests für diese Klasse.

Sie können mit zwei Konstrukteuren, wie im folgenden Code, um den Legacy-Code übergehen, bis Sie den Code Refactoring haben, die die Klasse konstruiert. Auf diese Weise können Sie leichter testen Foo und Sie können den Code halten, die Foo unverändert verwendet. Dann können Sie langsam den Code Refactoring, so dass der alte Konstruktor vollständig entfernt wird und alle Abhängigkeiten Abhängigkeit injiziert.

public class Foo {
  private final DataSource dataSource;
  public Foo() { // production code calls this - no changes needed to callers
    Context ctx = new InitialContext();
    this.dataSource = (DataSource) ctx.lookup(jndiName);
  }
  public Foo(DataSource dataSource) { // test code calls this
    this.dataSource = dataSource;
  }
  // methods that use dataSource
}

Aber bevor Sie das Refactoring tun beginnen, sollten Sie einige Integrationstests haben den Rücken zu decken. Ansonsten können Sie nicht wissen, ob auch die einfachen Refactorings, wie der Datasource-Lookup an den Konstruktor zu bewegen, etwas brechen. Dann, wenn der Code besser wird, mehr prüfbar, können Sie Unit-Tests schreiben. (Per Definition, wenn ein Test auf das Dateisystem, Netzwerk oder eine Datenbank berührt, es ist kein Unit-Test -. Es ist ein Integrationstest)

Der Vorteil von Unit-Tests ist, dass sie schnell ist - Hunderte oder Tausende pro Sekunde - und ist sehr konzentriert auf Tests nur ein Verhalten zu einer Zeit. Das macht es möglich, laufen dann oft (wenn Sie laufen alle Unit-Tests zögern nach einer Zeile zu ändern, sie laufen zu langsam), so dass Sie schnell Feedback. Und weil sie sehr konzentriert sind, werden Sie wissen, gerade indem Sie den Namen des fehlerhaften Test suchen, dass genau dort, wo in der Produktion Code der Fehler ist.

Der Vorteil von Integrationstests besteht darin, dass sie sicherstellen, dass alle Teile richtig zusammensteckt sind. Das ist auch wichtig, aber man kann sich nicht sehr oft laufen, weil Dinge wie die Datenbank zu berühren machen sie sehr langsam. Aber man sollte sie mindestens einmal pro Tag auf die kontinuierliche Integration Server noch ausgeführt werden.

Es gibt ein paar Möglichkeiten, dieses Problem zu lösen. Eine davon ist ein Context-Objekt der Klasse zu übergeben, wenn es unter Unit-Test ist. Wenn Sie nicht die Methodensignatur ändern können, Refactoring die Schaffung des inital Kontext zu einem geschützten Verfahren und eine Unterklasse testen, die die verspottete Kontext-Objekt zurückgibt, indem die Methode überschrieben. Das kann zumindest legt die Klasse unter Test, so dass Sie bessere Alternativen von dort Refactoring können.

Die nächste Option ist, um Datenbankverbindungen eine Fabrik zu machen, dass sagen kann, wenn sie in einem Container oder nicht, und führen Sie die entsprechende Sache in jedem Fall ist.

Eine Sache zu denken ist - wenn Sie diese Datenbankverbindung aus dem Behälter, was willst du damit tun? Es ist einfacher, aber es ist nicht ganz ein Gerät zu testen, ob Sie die gesamte Datenzugriffsschicht zu tragen haben.

Für weitere Hilfe in diese Richtung Legacy-Code unter Unit-Test zu bewegen, ich schlage vor, Sie schauen bei Michael Feathers Effektives Arbeiten mit Legacy Code .

Eine saubere Art und Weise, dies zu tun wäre Lifecycle Zuhörer in web.xml konfiguriert haben. Diese können globalen Flags gesetzt, wenn Sie wollen. Zum Beispiel könnten Sie definieren ein ServletContextListener in Ihrem web.xml und in der contextInitialized Methode, stellte ein globales Flag, das Sie in einem Container laufen. Wenn die globale Flag nicht gesetzt ist, dann sind Sie nicht in einem Container ausgeführt wird.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top