Anleitung zur Systemregelbibliothek

1. Übersicht

Manchmal müssen wir beim Schreiben von Komponententests Code testen, der direkt mit der Systemklasse interagiert . In der Regel in Anwendungen wie Befehlszeilentools, die System.exit direkt aufrufen oder Argumente mit System.in lesen .

In diesem Lernprogramm werden die häufigsten Funktionen einer übersichtlichen externen Bibliothek namens System Rules vorgestellt, die eine Reihe von JUnit-Regeln zum Testen von Code enthält, der die System- Klasse verwendet .

2. Maven-Abhängigkeiten

Fügen wir zunächst die Abhängigkeit von den Systemregeln zu unserer Datei pom.xml hinzu :

 com.github.stefanbirkner system-rules 1.19.0 

Wir werden auch die System-Lambda-Abhängigkeit hinzufügen, die auch von Maven Central erhältlich ist:

 com.github.stefanbirkner system-lambda 1.1.0 

Da System Rules JUnit5 nicht direkt unterstützt , haben wir die letzte Abhängigkeit hinzugefügt. Dadurch werden die System Lambda-Wrapper-Methoden bereitgestellt, die in Tests verwendet werden können. Es gibt eine auf Erweiterungen basierende Alternative zu diesem System namens System Stubs.

3. Arbeiten mit Systemeigenschaften

Zur schnellen Zusammenfassung verwendet die Java-Plattform ein Properties- Objekt, um Informationen zum lokalen System und zur Konfiguration bereitzustellen. Wir können die Eigenschaften leicht ausdrucken:

System.getProperties() .forEach((key, value) -> System.out.println(key + ": " + value));

Wie wir sehen können, enthalten die Eigenschaften Informationen wie den aktuellen Benutzer, die aktuelle Version der Java-Laufzeit und das Trennzeichen für den Dateipfadnamen:

java.version: 1.8.0_221 file.separator: / user.home: /Users/baeldung os.name: Mac OS X ...

Wir können auch unsere eigenen Systemeigenschaften mithilfe der System.setProperty- Methode festlegen . Bei der Arbeit mit Systemeigenschaften aus unseren Tests ist Vorsicht geboten, da diese Eigenschaften JVM-global sind.

Wenn wir beispielsweise eine Systemeigenschaft festlegen, sollten wir sicherstellen, dass die Eigenschaft nach Abschluss unseres Tests oder wenn ein Fehler auftritt, auf ihren ursprünglichen Wert zurückgesetzt wird. Dies kann manchmal zu umständlichem Einrichten und Herunterfahren von Code führen. Wenn wir dies jedoch vernachlässigen, kann dies zu unerwarteten Nebenwirkungen in unseren Tests führen.

Im nächsten Abschnitt erfahren Sie, wie wir nach Abschluss unserer Tests die Eigenschaften der Systemeigenschaften bereitstellen, bereinigen und sicherstellen können, dass sie wiederhergestellt werden.

4. Bereitstellen von Systemeigenschaften

Stellen wir uns vor, wir haben eine Systemeigenschaft log_dir, die den Speicherort unserer Protokolle enthält, und unsere Anwendung legt diesen Speicherort beim Start fest:

System.setProperty("log_dir", "/tmp/baeldung/logs");

4.1. Stellen Sie eine einzelne Eigenschaft bereit

Betrachten wir nun, dass wir aus unserem Unit-Test einen anderen Wert liefern möchten. Wir können dies mit der ProvideSystemProperty- Regel tun :

public class ProvidesSystemPropertyWithRuleUnitTest { @Rule public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources"); @Test public void givenProvideSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() { assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir")); } // unit test definition continues } 

Mit der ProvideSystemProperty- Regel können wir einen beliebigen Wert für eine bestimmte Systemeigenschaft festlegen , der aus unseren Tests verwendet werden soll. In diesem Beispiel setzen wir die Eigenschaft log_dir auf unser Verzeichnis test / resources und bestätigen in unserem Komponententest einfach, dass der Wert der Testeigenschaft erfolgreich bereitgestellt wurde.

Wenn wir dann den Wert der Eigenschaft log_dir ausdrucken, wenn unsere Testklasse abgeschlossen ist:

@AfterClass public static void tearDownAfterClass() throws Exception { System.out.println(System.getProperty("log_dir")); } 

Wir können sehen, dass der Wert der Immobilie auf ihren ursprünglichen Wert zurückgesetzt wurde:

/tmp/baeldung/logs

4.2. Mehrere Eigenschaften bereitstellen

Wenn wir mehrere Eigenschaften bereitstellen müssen, können wir die Methode und verwenden, um so viele Eigenschaftswerte miteinander zu verketten, wie wir für unseren Test benötigen:

@Rule public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources").and("another_property", "another_value")

4.3. Bereitstellen von Eigenschaften aus einer Datei

Ebenso haben wir auch die Möglichkeit, Eigenschaften aus einer Datei oder einer Klassenpfadressource mithilfe der ProvideSystemProperty- Regel bereitzustellen :

@Rule public final ProvideSystemProperty providesSystemPropertyFromFileRule = ProvideSystemProperty.fromResource("/test.properties"); @Test public void givenProvideSystemPropertyFromFile_whenGetName_thenNameIsProvidedSuccessfully() { assertEquals("name should be provided", "baeldung", System.getProperty("name")); assertEquals("version should be provided", "1.0", System.getProperty("version")); }

Im obigen Beispiel wird davon ausgegangen, dass sich im Klassenpfad eine Datei test.properties befindet :

name=baeldung version=1.0

4.4. Bereitstellen von Eigenschaften mit JUnit5 und Lambdas

Wie bereits erwähnt, können wir auch die System Lambda-Version der Bibliothek verwenden, um mit JUnit5 kompatible Tests zu implementieren.

Mal sehen, wie wir unseren Test mit dieser Version der Bibliothek implementieren:

@BeforeAll static void setUpBeforeClass() throws Exception { System.setProperty("log_dir", "/tmp/baeldung/logs"); } @Test void givenSetSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() throws Exception { restoreSystemProperties(() -> { System.setProperty("log_dir", "test/resources"); assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir")); }); assertEquals("log_dir should be provided", "/tmp/baeldung/logs", System.getProperty("log_dir")); }

In dieser Version können wir die Methode restoreSystemProperties verwenden, um eine bestimmte Anweisung auszuführen. In dieser Anweisung können wir die Werte einrichten und bereitstellen, die wir für unsere Systemeigenschaften benötigen . Wie wir sehen können, ist der Wert von log_dir nach Abschluss dieser Methode derselbe wie vor / tmp / baeldung / logs .

Leider gibt es keine integrierte Unterstützung für die Bereitstellung von Eigenschaften aus Dateien mit der Methode restoreSystemProperties .

5. Löschen der Systemeigenschaften

Manchmal möchten wir möglicherweise eine Reihe von Systemeigenschaften löschen, wenn unser Test beginnt, und ihre ursprünglichen Werte wiederherstellen, wenn der Test beendet ist, unabhängig davon, ob er bestanden wurde oder nicht.

Wir können die ClearSystemProperties- Regel für diesen Zweck verwenden:

@Rule public final ClearSystemProperties userNameIsClearedRule = new ClearSystemProperties("user.name"); @Test public void givenClearUsernameProperty_whenGetUserName_thenNull() { assertNull(System.getProperty("user.name")); }

Die Systemeigenschaft user.name ist eine der vordefinierten Systemeigenschaften, die den Benutzerkontonamen enthält. Wie im obigen Komponententest erwartet, löschen wir diese Eigenschaft und prüfen, ob sie in unserem Test leer ist.

Praktischerweise können wir auch mehrere Eigenschaftsnamen an den ClearSystemProperties- Konstruktor übergeben.

6. System.in verspotten

Von Zeit zu Zeit erstellen wir möglicherweise interaktive Befehlszeilenanwendungen, die aus System.in gelesen werden .

In diesem Abschnitt verwenden wir ein sehr einfaches Beispiel, in dem ein Vor- und Nachname aus der Standardeingabe gelesen und miteinander verknüpft werden:

private String getFullname() { try (Scanner scanner = new Scanner(System.in)) { String firstName = scanner.next(); String surname = scanner.next(); return String.join(" ", firstName, surname); } }

System Rules enthält die TextFromStandardInputStream- Regel, mit der wir die Zeilen angeben können, die beim Aufruf von System.in angegeben werden sollen :

@Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() { systemInMock.provideLines("Jonathan", "Cook"); assertEquals("Names should be concatenated", "Jonathan Cook", getFullname()); }

Wir erreichen dies durch die Verwendung von providesLines - Methode, die ein Parameter mehr als einen Wert zu ermöglichen , nimmt varargs angibt.

In diesem Beispiel stellen wir zwei Werte bereit , bevor wir die Methode getFullname aufrufen, auf die auf System.in verwiesen wird. Unsere beiden angegebenen Zeilenwerte werden bei jedem Aufruf von scanner.next () zurückgegeben .

Lassen Sie uns einen Blick darauf werfen, wie wir dasselbe in einer JUnit 5-Version des Tests mit System Lambda erreichen können:

@Test void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() throws Exception { withTextFromSystemIn("Jonathan", "Cook").execute(() -> { assertEquals("Names should be concatenated", "Jonathan Cook", getFullname()); }); }

In dieser Variante verwenden wir die ähnlich benannte withTextFromSystemIn- Methode, mit der wir die angegebenen System.in- Werte angeben können.

In beiden Fällen ist es wichtig zu erwähnen, dass nach Abschluss des Tests der ursprüngliche Wert von System.in wiederhergestellt wird.

7. Testen von System.out und System.err

In einem früheren Tutorial haben wir gesehen, wie Systemregeln verwendet werden, um System.out.println () zu testen .

Praktischerweise können wir einen nahezu identischen Ansatz zum Testen von Code anwenden, der mit dem Standardfehlerstrom interagiert. Diesmal verwenden wir die SystemErrRule :

@Rule public final SystemErrRule systemErrRule = new SystemErrRule().enableLog(); @Test public void givenSystemErrRule_whenInvokePrintln_thenLogSuccess() { printError("An Error occurred Baeldung Readers!!"); Assert.assertEquals("An Error occurred Baeldung Readers!!", systemErrRule.getLog().trim()); } private void printError(String output) { System.err.println(output); }

Nice! Using the SystemErrRule, we can intercept the writes to System.err. First, we start logging everything written to System.err by calling the enableLog method on our rule. Then we simply call getLog to get the text written to System.err since we called enableLog.

Now, let's implement the JUnit5 version of our test:

@Test void givenTapSystemErr_whenInvokePrintln_thenOutputIsReturnedSuccessfully() throws Exception { String text = tapSystemErr(() -> { printError("An error occurred Baeldung Readers!!"); }); Assert.assertEquals("An error occurred Baeldung Readers!!", text.trim()); }

In this version, we make use of the tapSystemErr method, which executes the statement and lets us capture the content passed to System.err.

8. Handling System.exit

Command-line applications typically terminate by calling System.exit. If we want to test such an application, it is likely that our test will terminate abnormally before it finishes when it encounters the code which calls System.exit.

Thankfully, System Rules provides a neat solution to handle this using the ExpectedSystemExit rule:

@Rule public final ExpectedSystemExit exitRule = ExpectedSystemExit.none(); @Test public void givenSystemExitRule_whenAppCallsSystemExit_thenExitRuleWorkssAsExpected() { exitRule.expectSystemExitWithStatus(1); exit(); } private void exit() { System.exit(1); }

Using the ExpectedSystemExit rule allows us to specify from our test the expected System.exit() call. In this simple example, we also check the expected status code using the expectSystemExitWithStatus method.

We can achieve something similar in our JUnit 5 version using the catchSystemExit method:

@Test void givenCatchSystemExit_whenAppCallsSystemExit_thenStatusIsReturnedSuccessfully() throws Exception { int statusCode = catchSystemExit(() -> { exit(); }); assertEquals("status code should be 1:", 1, statusCode); }

9. Fazit

Zusammenfassend haben wir in diesem Tutorial die Systemregeln-Bibliothek im Detail untersucht.

Zunächst wurde erklärt, wie Code getestet wird, der Systemeigenschaften verwendet. Dann haben wir uns angesehen, wie man die Standardausgabe und die Standardeingabe testet. Schließlich haben wir uns angesehen, wie mit Code umgegangen wird, der System.exit aus unseren Tests aufruft .

Die Systemregeln-Bibliothek bietet auch Unterstützung für die Bereitstellung von Umgebungsvariablen und speziellen Sicherheitsmanagern aus unseren Tests . Weitere Informationen finden Sie in der vollständigen Dokumentation.

Wie immer ist der vollständige Quellcode des Artikels auf GitHub verfügbar.