Ich versuche, einige JUnit-Tests für eine Methode zu erstellen, für die Benutzereingaben erforderlich sind. Die zu testende Methode sieht ungefähr wie folgt aus:
public static int testUserInput() {
Scanner keyboard = new Scanner(System.in);
System.out.println("Give a number between 1 and 10");
int input = keyboard.nextInt();
while (input < 1 || input > 10) {
System.out.println("Wrong number, try again.");
input = keyboard.nextInt();
}
return input;
}
Gibt es eine Möglichkeit, das Programm automatisch an ein int zu übergeben, anstatt dass ich oder jemand anderes dies manuell in der JUnit-Testmethode tut? Möchten Sie die Benutzereingaben simulieren?
Danke im Voraus.
java
eclipse
junit
user-input
Wimpey
quelle
quelle
Antworten:
Sie können System.in durch Ihren eigenen Stream ersetzen, indem Sie System.setIn (InputStream in) aufrufen . InputStream kann ein Byte-Array sein:
InputStream sysInBackup = System.in; // backup System.in to restore it later ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes()); System.setIn(in); // do your thing // optionally, reset System.in to its original System.setIn(sysInBackup);
Ein anderer Ansatz kann diese Methode testbarer machen, indem IN und OUT als Parameter übergeben werden:
public static int testUserInput(InputStream in,PrintStream out) { Scanner keyboard = new Scanner(in); out.println("Give a number between 1 and 10"); int input = keyboard.nextInt(); while (input < 1 || input > 10) { out.println("Wrong number, try again."); input = keyboard.nextInt(); } return input; }
quelle
\n
aber das macht keinen UnterschiedByteArrayInputStream in = new ByteArrayInputStream(("1" + System.lineSeparator() + "2").getBytes());
, um mehrere Eingaben für verschiedenekeyboard.nextLine()
Anrufe abzurufen .Um Ihren Code zu testen, sollten Sie einen Wrapper für Systemeingabe- / Ausgabefunktionen erstellen. Sie können dies mithilfe der Abhängigkeitsinjektion tun und uns eine Klasse geben, die nach neuen Ganzzahlen fragen kann:
public static class IntegerAsker { private final Scanner scanner; private final PrintStream out; public IntegerAsker(InputStream in, PrintStream out) { scanner = new Scanner(in); this.out = out; } public int ask(String message) { out.println(message); return scanner.nextInt(); } }
Dann können Sie Tests für Ihre Funktion erstellen, indem Sie ein Mock-Framework verwenden (ich verwende Mockito):
@Test public void getsIntegerWhenWithinBoundsOfOneToTen() throws Exception { IntegerAsker asker = mock(IntegerAsker.class); when(asker.ask(anyString())).thenReturn(3); assertEquals(getBoundIntegerFromUser(asker), 3); } @Test public void asksForNewIntegerWhenOutsideBoundsOfOneToTen() throws Exception { IntegerAsker asker = mock(IntegerAsker.class); when(asker.ask("Give a number between 1 and 10")).thenReturn(99); when(asker.ask("Wrong number, try again.")).thenReturn(3); getBoundIntegerFromUser(asker); verify(asker).ask("Wrong number, try again."); }
Schreiben Sie dann Ihre Funktion, die die Tests besteht. Die Funktion ist viel sauberer, da Sie die Duplizierung von Abfragen / Abrufen von Ganzzahlen entfernen können und die tatsächlichen Systemaufrufe gekapselt sind.
public static void main(String[] args) { getBoundIntegerFromUser(new IntegerAsker(System.in, System.out)); } public static int getBoundIntegerFromUser(IntegerAsker asker) { int input = asker.ask("Give a number between 1 and 10"); while (input < 1 || input > 10) input = asker.ask("Wrong number, try again."); return input; }
Dies mag für Ihr kleines Beispiel wie ein Overkill erscheinen, aber wenn Sie eine größere Anwendung erstellen, kann sich eine solche Entwicklung ziemlich schnell auszahlen.
quelle
Eine übliche Methode zum Testen eines ähnlichen Codes besteht darin, eine Methode zu extrahieren, die einen Scanner und einen PrintWriter enthält, ähnlich wie bei dieser StackOverflow-Antwort , und Folgendes zu testen:
public void processUserInput() { processUserInput(new Scanner(System.in), System.out); } /** For testing. Package-private if possible. */ public void processUserInput(Scanner scanner, PrintWriter output) { output.println("Give a number between 1 and 10"); int input = scanner.nextInt(); while (input < 1 || input > 10) { output.println("Wrong number, try again."); input = scanner.nextInt(); } return input; }
Beachten Sie, dass Sie Ihre Ausgabe erst am Ende lesen können und alle Ihre Eingaben im Voraus angeben müssen:
@Test public void shouldProcessUserInput() { StringWriter output = new StringWriter(); String input = "11\n" // "Wrong number, try again." + "10\n"; assertEquals(10, systemUnderTest.processUserInput( new Scanner(input), new PrintWriter(output))); assertThat(output.toString(), contains("Wrong number, try again."));); }
Anstatt eine Überladungsmethode zu erstellen, können Sie natürlich auch "Scanner" und "Ausgabe" als veränderbare Felder in Ihrem zu testenden System beibehalten. Ich mag es, den Unterricht so staatenlos wie möglich zu halten, aber das ist kein großes Zugeständnis, wenn es Ihnen oder Ihren Mitarbeitern / Ausbildern wichtig ist.
Sie können Ihren Testcode auch in dasselbe Java-Paket wie den zu testenden Code einfügen (auch wenn er sich in einem anderen Quellordner befindet), wodurch Sie die Sichtbarkeit der Überladung mit zwei Parametern lockern können, um paketprivat zu sein.
quelle
Ich habe es geschafft, einen einfacheren Weg zu finden. Sie müssen jedoch die externe Bibliothek System.rules von @Stefan Birkner verwenden
Ich habe nur das dort bereitgestellte Beispiel genommen, ich denke, es hätte nicht einfacher sein können:
import java.util.Scanner; public class Summarize { public static int sumOfNumbersFromSystemIn() { Scanner scanner = new Scanner(System.in); int firstSummand = scanner.nextInt(); int secondSummand = scanner.nextInt(); return firstSummand + secondSummand; } }
Prüfung
import static org.junit.Assert.*; import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.*; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.TextFromStandardInputStream; public class SummarizeTest { @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void summarizesTwoNumbers() { systemInMock.provideLines("1", "2"); assertEquals(3, Summarize.sumOfNumbersFromSystemIn()); } }
Das Problem ist jedoch in meinem Fall, dass meine zweite Eingabe Leerzeichen enthält und der gesamte Eingabestream null ist!
quelle
inputEntry = scanner.nextLine();
bei Verwendung mitSystem.in
immer auf den Benutzer gewartet wird (und eine leere Zeile als leer akzeptiert wirdString
) ... wohingegen, wenn Sie Leitungen verwenden,systemInMock.provideLines()
dies verwendet wird,NoSuchElementException
wenn die Zeilen ausgehen. Dies macht es wirklich einfach, den App-Code nicht zu stark zu "verzerren", um den Tests gerecht zu werden.systemInMock.provideLines("1", "2");
2. Verwenden von System.setIn ohne externe Bibliotheken:String data2 = "1 2";
System.setIn(new ByteArrayInputStream(data2.getBytes()));
Sie können beginnen, indem Sie die Logik, die die Nummer von der Tastatur abruft, in eine eigene Methode extrahieren. Anschließend können Sie die Validierungslogik testen, ohne sich um die Tastatur kümmern zu müssen. Um den Aufruf von keyboard.nextInt () zu testen, sollten Sie ein Scheinobjekt verwenden.
quelle
Ich habe das Problem beim Lesen von stdin behoben, um eine Konsole zu simulieren ...
Meine Probleme waren, ich würde gerne versuchen, in JUnit die Konsole zu testen, um ein bestimmtes Objekt zu erstellen ...
Das Problem ist wie alles, was Sie sagen: Wie kann ich im Stdin vom JUnit-Test schreiben?
Dann lerne ich am College etwas über Weiterleitungen, wie Sie sagen, System.setIn (InputStream) ändert den Standard-Dateiskriptor und Sie können dann schreiben ...
Aber es gibt noch ein Problem zu beheben ... den JUnit-Testblock, der darauf wartet, von Ihrem neuen InputStream gelesen zu werden. Sie müssen also einen Thread erstellen, der aus dem InputStream und aus dem JUnit-Test-Thread gelesen werden kann. Schreiben Sie in den neuen Stdin ... Zuerst müssen Sie Schreiben Sie in das Stdin, denn wenn Sie später schreiben oder den Thread erstellen, um aus dem Stdin zu lesen, haben Sie wahrscheinlich Race-Bedingungen ... Sie können vor dem Lesen in den InputStream schreiben oder vor dem Schreiben aus dem InputStream lesen ...
Dies ist mein Code, meine Englischkenntnisse sind schlecht. Ich hoffe, Sie können das Problem und die Lösung für die Simulation des Schreibens in stdin aus dem JUnit-Test verstehen.
private void readFromConsole(String data) throws InterruptedException { System.setIn(new ByteArrayInputStream(data.getBytes())); Thread rC = new Thread() { @Override public void run() { study = new Study(); study.read(System.in); } }; rC.start(); rC.join(); }
quelle
Ich fand es hilfreich, eine Schnittstelle zu erstellen, die Methoden definiert, die java.io.Console ähneln, und diese dann zum Lesen oder Schreiben in System.out zu verwenden. Die eigentliche Implementierung wird an System.console () delegiert, während Ihre JUnit-Version ein Scheinobjekt mit vordefinierten Eingaben und erwarteten Antworten sein kann.
Beispielsweise würden Sie eine MockConsole erstellen, die die vordefinierten Eingaben des Benutzers enthält. Die Scheinimplementierung würde bei jedem Aufruf von readLine eine Eingabezeichenfolge aus der Liste streichen. Es würde auch die gesamte Ausgabe sammeln, die in eine Liste von Antworten geschrieben wurde. Wenn am Ende des Tests alles gut gegangen wäre, wäre Ihre gesamte Eingabe gelesen worden, und Sie können die Ausgabe bestätigen.
quelle