Java Input/Output Tests Junit?
Hallo,
Ich habe folgendes Problem: Ich habe eine Klasse, die die Benutzerinteraktion (Input/Output) handhabt und ich muss Junit4 Tests dafür schreiben.
Die Klasse, die ich testen möchte, sieht in etwa so aus:
package program;
// imports
public class UserInteraction {
private Program program;
private UserInteraction(){
program = new Program();
}
// Menu display
private void start() {
while (!program.getProgramStarted) {
String input = readInput();
switch (input) {
case "1":
addUser();
break;
case "2":
deleteUser();
break;
case "3":
// ...
default:
System.err.println("Unknown option");
break;
}
}
}
private void addUser() {
// Output
String username = readInput();
program.addUser(username);
System.out.println("User " + username + "added");
}
private void deleteUser() {
// Output
String username = readInput();
program.deleteUser(username);
System.out.println("User " + username + "deleted");
}
@SuppressWarnings("resource")
private String readInput() {
return (new Scanner(System.in)).nextLine();
}
public void main(String[] args) {
UserInteraction ui = new UserInteraction();
ui.start();
}
}
Die Program Klasse so:
package program;
// imports
public class Program {
// fields
private User currentUser;
private List<User> user = new ArrayList<User>();
// constructor
public void addUser(String name) throws Exception {
if (user.size() >= 6) {
throw new Exception();
}
int newID = user.size() + 1;
user.add(new User(name, newID));
}
public String[] getALlUser() {
int counter = 0;
String[] userArray = new String[user.size()];
for (User s : user) {
userArray[counter++] = s.toString();
}
return userArray;
}
}
Nun möchte ich zum Beispiel testen, ob die folgende Eingabe korrekt verarbeitet wird:
1
John
=> Benutzer John hinzugefügt
Zu diesem Zweck habe ich einen Junit-Test geschrieben, der wie folgt aussieht:
package tests;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.Test;
import program.*;
public class UserInteractionTest {
private final InputStream originalIn = System.in;
private final PrintStream originalOut = System.out;
private Program program;
@After
public void restoreStreams() {
System.setIn(originalIn);
System.setOut(originalOut);
}
@Test
public void testCreateUser() throws Exception {
String input = "1\nJohn\n";
ByteArrayInputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent)); // Leitet System.out um
program = new Program();
try {
program.main(null);
} finally {
System.setIn(originalIn);
}
String expectedOutput = "User John added";
assertTrue("Output not found", outContent.toString().contains(expectedOutput));
}
}
Die Eingabe ("1\n") wird gemacht, aber danach wird keine Eingabe eingefügt, so dass der Test hängen bleibt.
Weiß jemand, wie man dieses Problem lösen kann? Oder gibt es vielleicht eine bessere Möglichkeit, die Eingabe/Ausgabe meines Java-Programms zu testen/zu überprüfen?
Vielen Dank im Voraus!
1 Antwort
Ganz allgemein: ein vernünftig testbares Programm hat eben keine hartkodierten Abhängigkeiten auf bestimmte Ressourcen wie solche Streams. Der Trick mit dem Umhängen von System.in ist halt eher ein...fauler Trick mit diversen Nachteilen (z.B. gilt es prozessweit, was bei parallelen Tests zu sehr unschönen Effekten führt).
Sinnvollerweise könnte man also der UserInteraction direkt jene Streams übergeben, die zu verwenden sind, Default ist halt weiterhin System.in/System.out. Man könnte auch noch weiter gehen und die Ein- und Ausgabe durch Strings ersetzen, die dann injiziert bzw. abgefangen werden.
Aber wenn das nicht geht:
String input = "1\nJohn\n";
Hier hast du hartkodierte Zeilenenden nach Unix-Manier. Auf Windows ist aber \r\n nötig. Auf welchem OS testest du? Mit printf() kannst du %n verwenden, oder du kannst den jeweiligen Line Separator mit https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/System.html#lineSeparator() holen.
Mir ist auch unklar, warum du hier Program instanziierst, statt UserInteraction. Offenbar ist die doch die eigentliche Main-Klasse, die dann ihrerseits Program instanziiert?
program = new Program();
try {
program.main(null);
Dann noch ein Thema:
private String readInput() {
return (new Scanner(System.in)).nextLine();
}
Hier wird für jede einzelne Eingabe sinnlos ein neues Scanner-Objekt erzeugt. Wozu?
Und dann solltest du noch bedenken, dass dein Programm auch stderr für Ausgaben verwendet (für Fehlermeldungen).
Das sind die Dinge, die aufs erste Drüberlesen auffallen.