Java - Wie kann ein Ressourcendateipfad während des Komponententests gegen eine Testdatei ausgetauscht werden?

8

Ich habe eine Ressourcendatei mit einigen Einstellungen. Ich habe eine ResourceLoader-Klasse, die die Einstellungen aus dieser Datei lädt. Diese Klasse ist derzeit eine eifrig instanziierte Singleton-Klasse. Sobald diese Klasse geladen wird, liest sie die Einstellungen aus der Datei (Dateipfad, der als konstantes Feld in einer anderen Klasse gespeichert ist). Einige dieser Einstellungen sind nicht für Unit-Tests geeignet. ZB habe ich in dieser Datei eine Thread-Ruhezeit, die Stunden für den Produktionscode betragen kann, aber ich möchte, dass sie für Unit-Tests einige Millisekunden beträgt. Ich habe also eine andere Testressourcendatei, die andere Werte hat. Meine Frage ist, wie ich beim Testen von Einheiten die Hauptressourcendatei mit dieser Testdatei austauschen soll. Das Projekt ist ein Maven-Projekt und ich verwende testng als Test-Framework. Dies sind einige der Ansätze, die ich '

  1. Verwenden Sie @BeforeSuite und ändern Sie die Konstantenvariable FilePath so, dass sie auf die Testdatei verweist. Verwenden Sie @AfterSuite, um sie wieder auf die Originaldatei zu verweisen. Dies scheint zu funktionieren, aber ich denke, da die ResourceLoader-Klasse eifrig instanziiert wird, gibt es keine Garantie dafür, dass die @ BeforeSuite-Methode immer ausgeführt wird, bevor die ResourceLoader-Klasse geladen wird, und daher möglicherweise alte Eigenschaften geladen werden, bevor der Dateipfad geändert wird. Obwohl die meisten Compiler eine Klasse nur laden, wenn dies erforderlich ist, bin ich mir nicht sicher, ob dies eine Java-Spezifikationsanforderung ist. Theoretisch funktioniert dies möglicherweise nicht für alle Java-Compiler.

  2. Übergeben Sie den Pfad der Ressourcendatei als Befehlszeilenargument. Ich kann den Pfad der Testressourcendatei als Befehlszeilenargument in der todsicheren Konfiguration im POM hinzufügen. Das scheint etwas übertrieben.

  3. Verwenden Sie den Ansatz in 1. und machen Sie ResourceLoader faul instanziiert. Dies garantiert, dass ResourceLoader die richtige Datei lädt, wenn @BeforeMethod vor dem ersten Aufruf von ResourceLoader.getInstance (). GetProperty (..) aufgerufen wird. Dies scheint besser zu sein als die ersten beiden Ansätze, aber ich denke, dass es hässlich ist, eine Singleton-Klasse faul instanziiert zu machen, da ich kein einfaches Muster wie eine Aufzählung und dergleichen verwenden kann (wie es bei eifriger Instanziierung der Fall ist).

Dies scheint ein häufiges Szenario zu sein. Wie geht man am häufigsten vor?

Marko Cain
quelle
Ist eine Überarbeitung Ihrer Anwendung zur Verwendung einer besseren Konfigurationsbibliothek eine Option?
Thorbjørn Ravn Andersen
Ja, ich möchte nur die Standardmethode kennen. Ich bin offen für die Überarbeitung der Anwendung.
Marko Cain
Die einzige Standardmethode ist die Verwendung von Umgebungs- und / oder Systemeigenschaften. Dies ist normalerweise zu einschränkend, da es schwierig ist, sie beispielsweise in Tests über die Befehlszeile bereitzustellen und zu überschreiben. Ich würde vorschlagen, dass Sie Ihrer Frage Informationen hinzufügen, was Sie benötigen und wie Sie Ihre Anwendung mit einigen Anwendungsfällen konfigurieren können. Möglicherweise möchten Sie die Konfigurationswerte im Konstruktor der Klassen übergeben, die sie benötigen.
Thorbjørn Ravn Andersen

Antworten:

7

Alle Singletons, die entweder eifrig oder träge instanziiert werden, sind Anti-Patterns . Die Verwendung von Singletons erschwert das Testen von Einheiten, da es keine einfache Möglichkeit gibt, Singleton zu verspotten.

Mock statische Methode

Eine Problemumgehung besteht darin, PowerMock zu verwenden, um die statische Methode zu verspotten, die eine Singleton-Instanz zurückgibt.

Verwenden Sie die Abhängigkeitsinjektion

Eine bessere Lösung ist die Verwendung der Abhängigkeitsinjektion. Wenn Sie bereits ein Abhängigkeitsinjektionsframework (z. B. Spring, CDI) verwenden, überarbeiten Sie den Code, um ResourceLoadereine verwaltete Bean mit Scope-Singleton zu erstellen .

Wenn Sie kein Abhängigkeitsinjektionsframework verwenden, besteht ein einfaches Refactoring darin, mit dem Singleton Änderungen an allen Klassen vorzunehmen ResourceLoader:

public class MyService {

  public MyService() {
    this(ResourceLoader.getInstance());
  }

  public MyService(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
}

Und dann in Unit-Tests ResourceLoadermit Mockito verspotten

ResourceLoader resourceLoader = mock(ResourceLoader.class);
when(ResourceLoader.getProperty("my-property")).thenReturn("10");
MyService myService = new MyService(resourceLoader);

Konfiguration externalisieren

Ein anderer Ansatz besteht darin, eine Datei mit Testeinstellungen unter zu platzieren src/test/resources. Wenn Sie Einstellungen in src/main/resources/application.propertiesspeichern, src/test/resources/application.propertieswird diese von einer Datei überschrieben.

Es ist auch eine gute Idee, die Konfiguration in eine Datei zu verlagern, die nicht in einer JAR gepackt ist. Auf diese Weise enthält die Datei src/main/resources/application.propertiesStandardeigenschaften, und eine mit dem Befehlszeilenparameter übergebene Datei überschreibt diese Eigenschaften. Daher wird eine Datei mit Testeigenschaften auch als Befehlszeilenparameter übergeben. Sehen Sie, wie Spring mit der externalisierten Konfiguration umgeht .

Verwenden Sie die Java-Systemeigenschaften

Noch einfacher ist es, das Überschreiben von Standardeigenschaften mit Systemeigenschaften in der Methode zuzulassen ResourceLoader.getInstance().getProperty()und die Testeigenschaften auf diese Weise zu bestehen

public String getProperty(String name) {
  // defaultProperties are loaded from a file on a file system:
  // defaultProperties.load(new FileInputStream(new File(filePath)));
  // or from a file in the classpath:
  // defaultProperties.load(ResourceLoader.class.getResourceAsStream(filePath));
  return System.getProperty(name, defaultProperties.get(name));
}
Evgeniy Khyst
quelle
0

Überprüfen Sie, ob Sie in jUnit

Sie können zur Laufzeit auch überprüfen, ob jUnit ausgeführt wird, und dann den Pfad austauschen. Das würde so funktionieren ( ungetestet ):

public static funktionToTest() {
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
    List<StackTraceElement> list = Arrays.asList(stackTraceElements);
    String path = "/origin/path/to/your/file"; // here can you set the default path to your rescource
    for (StackTraceElement stackTraceElement : list) {
        if (stackTraceElement.getClassName().startsWith("org.junit.")) {
            path = "/path/only/in/jUnit/test"; // and here the normal path
        }           
    }
    //do what you want with the path
}
scolastico
quelle