Ich habe ein Java-Befehlszeilenprogramm. Ich möchte einen JUnit-Testfall erstellen, um ihn simulieren zu können System.in. Denn wenn mein Programm läuft, kommt es in die While-Schleife und wartet auf Eingaben von Benutzern. Wie simuliere ich das in JUnit?
Ein Wechsel ist technisch möglich System.in, aber im Allgemeinen wäre es robuster, es nicht direkt in Ihrem Code aufzurufen, sondern eine indirekte Ebene hinzuzufügen, sodass die Eingabequelle von einem Punkt in Ihrer Anwendung aus gesteuert wird. Wie Sie das genau tun, ist ein Implementierungsdetail – die Vorschläge zur Abhängigkeitsinjektion sind in Ordnung, aber Sie müssen nicht unbedingt Frameworks von Drittanbietern einführen. Sie könnten beispielsweise einen I/O-Kontext aus dem aufrufenden Code herumreichen.
Basierend auf der Antwort von @ McDowell und einer weiteren Antwort, die zeigt, wie System.out getestet wird, möchte ich meine Lösung teilen, um einem Programm eine Eingabe zu geben und seine Ausgabe zu testen.
Als Referenz verwende ich JUnit 4.12.
Nehmen wir an, wir haben dieses Programm, das einfach die Eingabe in die Ausgabe repliziert:
import java.util.Scanner;
public class SimpleProgram {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print(scanner.next());
scanner.close();
}
}
Um es zu testen, können wir die folgende Klasse verwenden:
import static org.junit.Assert.*;
import java.io.*;
import org.junit.*;
public class SimpleProgramTest {
private final InputStream systemIn = System.in;
private final PrintStream systemOut = System.out;
private ByteArrayInputStream testIn;
private ByteArrayOutputStream testOut;
@Before
public void setUpOutput() {
testOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(testOut));
}
private void provideInput(String data) {
testIn = new ByteArrayInputStream(data.getBytes());
System.setIn(testIn);
}
private String getOutput() {
return testOut.toString();
}
@After
public void restoreSystemInputOutput() {
System.setIn(systemIn);
System.setOut(systemOut);
}
@Test
public void testCase1() {
final String testString = "Hello!";
provideInput(testString);
SimpleProgram.main(new String[0]);
assertEquals(testString, getOutput());
}
}
Ich werde nicht viel erklären, weil ich glaube, dass der Code lesbar ist und ich meine Quellen zitiert habe.
Wenn JUnit läuft testCase1()werden die Hilfsmethoden in der Reihenfolge aufgerufen, in der sie erscheinen:
setUpOutput()wegen der @Before Anmerkung
provideInput(String data)angerufen von testCase1()
getOutput()angerufen von testCase1()
restoreSystemInputOutput()wegen der @After Anmerkung
Ich habe nicht getestet System.err weil ich es nicht brauchte, aber es sollte einfach zu implementieren sein, ähnlich wie beim Testen System.out.
Wie würden Sie mit mehreren Eingaben fortfahren?
– Schnurrbart
4. Juli 18 um 22:28 Uhr
Für das, was ich brauchte, war es genug, um eine zu bestehen String mit Newline-Zeichen zu provideInput(String data). Für mein Programm, das auf einer Befehlszeilenschnittstelle ausgeführt wird, gibt der Benutzer jedes Mal Text ein und klickt Enter, wird dieser Text als neue Eingabe behandelt. bitte beachten Sie SimpleProgram verwendet die Java SE Scanner Klasse, um die Eingaben des Benutzers zu lesen.
– António Medeiros
5. Juli 18 um 17:27 Uhr
Ich hätte also anrufen können provideInput("Input 1nInput 2nInput 3"); stattdessen.
– António Medeiros
5. Juli 18 um 17:30 Uhr
Yishai
Es gibt einige Möglichkeiten, dies anzugehen. Der vollständigste Weg besteht darin, einen InputStream zu übergeben, während die zu testende Klasse ausgeführt wird, bei dem es sich um einen gefälschten InputStream handelt, der simulierte Daten an Ihre Klasse übergibt. Sie können sich ein Abhängigkeitsinjektions-Framework (wie Google Guice) ansehen, wenn Sie dies häufig in Ihrem Code tun müssen, aber der einfache Weg ist:
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
Im Test würden Sie den Konstruktor aufrufen, der den Eingabestrom entgegennimmt. Sie machen dieses Konstruktorpaket sogar privat und legen den Test in dasselbe Paket, sodass anderer Code im Allgemeinen nicht in Betracht zieht, es zu verwenden.
+1. Ich bin da bei dir. Ich würde noch einen Schritt weiter gehen: InputData als High-Level-Wrapper herum InputStream Beim Komponententest sollten Sie sich mehr darum kümmern, was Ihre Klasse tut, und nicht wirklich um die Integration.
– Oscar Ryz
30. Oktober 09 um 5:33 Uhr
Anstatt die Variable zu erstellen systemIn Sie könnten die Methode verwenden System.setIn(in). Sie können also anrufen System.in normal, aber mit der aktualisierten Version.
– Larakonda
22. März 17 um 3:37 Uhr
Versuchen Sie, Ihren Code für die Verwendung umzugestalten Abhängigkeitsspritze. Anstatt eine Methode zu haben, die verwendet System.in direkt, lassen Sie die Methode an akzeptieren InputStream als Argument. Dann kannst du in deinem junit-Test einen Test bestehen InputStream Umsetzung statt System.in.
Sie können einen klaren Test für die Befehlszeilenschnittstelle schreiben, indem Sie die verwenden TextFromStandardInputStream Regel der Systemregeln Bücherei.
public void MyTest {
@Rule
public final TextFromStandardInputStream systemInMock
= emptyStandardInputStream();
@Test
public void readTextFromStandardInputStream() {
systemInMock.provideLines("foo");
Scanner scanner = new Scanner(System.in);
assertEquals("foo", scanner.nextLine());
}
}
Vollständige Offenlegung: Ich bin der Autor dieser Bibliothek.
Ich habe gerade versucht, Ihre Bibliothek zu verwenden, was sehr gut schien … aber dann habe ich versucht, sie mit Rule public MockitoRule rule = MockitoJUnit.rule(); zu verwenden, und es schien, soweit ich es versuchte, dass die beiden nicht sein konnten kombiniert … Also konnte ich es nicht mit Injektionen kombinieren …
– Mike Nagetier
22. Januar 17 um 18:59 Uhr
Das sollte nicht der Fall sein. Könnten Sie bitte ein Problem für die Systemregelbibliothek erstellen und Ihr fehlerhaftes Beispiel angeben.
– Stefan Birkner
23. Januar 17 um 22:10 Uhr
Danke … jetzt weiß ich, dass es funktionieren soll. Ich werde es untersuchen und gegebenenfalls ein Problem erstellen. Es schien das zu sein TextFromStandardInputStream womit ich ein Problem hatte, aber die SystemOutRule funktionierte OK.
– Mike Nagetier
24. Januar 17 um 7:59 Uhr
Dies ist eine großartige Bibliothek, aber beachten Sie, dass sie nicht mit junit5 kompatibel ist. Ich musste zu junit4 zurückkehren, um es zum Laufen zu bringen. Es ist ein bekannter Fehler, siehe das Git-Repo, das in Stefans Kommentar verlinkt ist
– Daniel
19. Januar 18 um 15:23 Uhr
@Daniel, es gibt jetzt System-Lambda, das mit junit5 und Testing kompatibel ist.
– MiB
13. September 21 um 3:51 Uhr
Sie könnten eine benutzerdefinierte erstellen InputStreamund befestigen Sie es an der System Klasse
class FakeInputStream extends InputStream {
public int read() {
return -1;
}
}
Und dann verwenden Sie es mit Ihrem Scanner
System.in = new FakeInputStream();
Vor:
InputStream in = System.in;
...
Scanner scanner = new Scanner( in );
Nach:
InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );
Obwohl ich denke, Sie sollten besser testen, wie Ihre Klasse mit den aus dem Eingabestrom gelesenen Daten arbeiten soll und nicht wirklich, wie sie von dort gelesen werden.
Ich habe gerade versucht, Ihre Bibliothek zu verwenden, was sehr gut schien … aber dann habe ich versucht, sie mit Rule public MockitoRule rule = MockitoJUnit.rule(); zu verwenden, und es schien, soweit ich es versuchte, dass die beiden nicht sein konnten kombiniert … Also konnte ich es nicht mit Injektionen kombinieren …
– Mike Nagetier
22. Januar 17 um 18:59 Uhr
Das sollte nicht der Fall sein. Könnten Sie bitte ein Problem für die Systemregelbibliothek erstellen und Ihr fehlerhaftes Beispiel angeben.
– Stefan Birkner
23. Januar 17 um 22:10 Uhr
Danke … jetzt weiß ich, dass es funktionieren soll. Ich werde es untersuchen und gegebenenfalls ein Problem erstellen. Es schien das zu sein TextFromStandardInputStream womit ich ein Problem hatte, aber die SystemOutRule funktionierte OK.
– Mike Nagetier
24. Januar 17 um 7:59 Uhr
Dies ist eine großartige Bibliothek, aber beachten Sie, dass sie nicht mit junit5 kompatibel ist. Ich musste zu junit4 zurückkehren, um es zum Laufen zu bringen. Es ist ein bekannter Fehler, siehe das Git-Repo, das in Stefans Kommentar verlinkt ist
– Daniel
19. Januar 18 um 15:23 Uhr
@Daniel, es gibt jetzt System-Lambda, das mit junit5 und Testing kompatibel ist.
– MiB
13. September 21 um 3:51 Uhr
Das Problem mit BufferedReader.readLine() ist, dass es sich um eine Sperrmethode handelt, die auf Benutzereingaben wartet. Es scheint mir, dass Sie das nicht besonders simulieren möchten (dh Sie möchten, dass Tests schnell sind). Aber in einem Testkontext kehrt es immer wieder zurück null mit hoher Geschwindigkeit beim Testen, was lästig ist.
Für einen Puristen kann man das machen getInputLine unten Paket-privat, und verspotten Sie es: easy-peezy.
… müssten Sie sicherstellen, dass Sie eine Möglichkeit haben, (normalerweise) eine Schleife der Benutzerinteraktion mit der App zu stoppen. Sie müssten auch damit klarkommen, dass Ihre “Eingabeleitungen” immer gleich bleiben würden, bis Sie die irgendwie geändert haben doReturn Ihres Spotts: kaum typisch für Benutzereingaben.
Für einen Nicht-Puristen, der sich das Leben leicht machen möchte (und lesbare Tests erstellen möchte), könnten Sie all diese Dinge unten in Ihren App-Code einfügen:
private Deque<String> inputLinesDeque;
void setInputLines(List<String> inputLines) {
inputLinesDeque = new ArrayDeque<String>(inputLines);
}
private String getInputLine() throws Exception {
if (inputLinesDeque == null) {
// ... i.e. normal case, during app run: this is then a blocking method
return br.readLine();
}
String nextLine = null;
try {
nextLine = inputLinesDeque.pop();
} catch (NoSuchElementException e) {
// when the Deque runs dry the line returned is a "poison pill",
// signalling to the caller method that the input is finished
return "q";
}
return nextLine;
}
baeldung.com/java-testing-system-out-println
– ahmednabil88
20. März 21 um 21:41 Uhr