Testy JUnit z symulowanym wejściem użytkownika
Próbuję stworzyć testy JUnit dla metody wymagającej wprowadzania danych przez użytkownika. Testowana metoda wygląda nieco jak następująca metoda:
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;
}
Czy jest możliwy sposób, aby automatycznie przekazać programowi int zamiast mnie lub kogoś innego robiącego to ręcznie w metodzie testu JUnit? Jak symulowanie wejścia użytkownika?
Z góry dzięki.7 answers
Możesz zastąpić System.in z własnym strumieniem przez wywołanie systemu.setIn (InputStream in) . Strumień wejściowy może być tablicą bajtów:
ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);
// do your thing
// optionally, reset System.in to its original
System.setIn(System.in)
Różne podejście może sprawić, że ta metoda będzie bardziej testowalna, przekazując i wychodząc jako parametry:
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-14 12:36:02
Aby przetestować kod, należy utworzyć wrapper dla funkcji wejścia/wyjścia systemu. Możesz to zrobić używając dependency injection, dając nam klasę, która może poprosić o nowe liczby całkowite:
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();
}
}
Następnie możesz utworzyć testy dla swojej funkcji, używając mock frameworku (ja używam 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.");
}
Następnie napisz swoją funkcję, która przejdzie testy. Funkcja jest znacznie czystsza, ponieważ można usunąć duplikację zapytania/uzyskania liczby całkowitej, a rzeczywiste wywołania systemowe są zamknięte.
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;
}
Może to wydawać się przesadą dla Twojego małego przykładu, ale jeśli budujesz większą aplikację rozwijającą się w ten sposób, może się to dość szybko opłacić.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-21 10:13:42
Jednym z powszechnych sposobów testowania podobnego kodu byłoby wyodrębnienie metody, która zajmuje skaner i drukarkę, podobnej do tej odpowiedzi StackOverflow , i przetestowanie, że:
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;
}
Pamiętaj, że nie będziesz w stanie odczytać danych wyjściowych do końca i będziesz musiał podać wszystkie dane wejściowe z góry:
@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.")););
}
Oczywiście, zamiast tworzyć metodę przeciążania, możesz również testować "skaner" i "wyjście" jako zmienne pola w systemie. Lubię prowadzenie zajęć w sposób jak najbardziej bezpaństwowy, ale nie jest to zbyt duże ustępstwo, jeśli ma to znaczenie dla Ciebie lub Twoich współpracowników / instruktora.
Możesz również umieścić swój kod testowy w tym samym pakiecie Javy, co Kod testowany (nawet jeśli znajduje się w innym folderze źródłowym), co pozwala zmniejszyć widoczność przeciążenia dwóch parametrów jako pakiet-prywatny.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:16
Udało mi się znaleźć prostszy sposób. Musisz jednak użyć zewnętrznej biblioteki Systemu.rules By @ Stefan Birkner
Wzięłam tylko podany tam przykład, myślę, że nie mogło być prościej:)
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;
}
}
Test
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());
}
}
Problem jednak w moim przypadku moje drugie wejście ma spacje i to sprawia, że cały strumień wejściowy jest null !
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-15 20:16:06
Możesz zacząć od wyodrębnienia logiki, która pobiera numer z klawiatury do własnej metody. Następnie możesz przetestować logikę walidacji bez martwienia się o klawiaturę. W celu przetestowania klawiatury.wywołanie nextInt() warto rozważyć użycie mock obiektu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-20 18:43:03
Uważam, że pomocne jest stworzenie interfejsu, który definiuje metody podobne do java. io. Console, a następnie użyć go do odczytu lub zapisu do systemu.Wynocha. Prawdziwa implementacja zostanie przekazana do systemu.console (), natomiast wersja JUnit może być obiektem mock z canned input i oczekiwanymi odpowiedziami.
Na przykład, można skonstruować MockConsole, który zawiera puszkowane dane wejściowe od użytkownika. Przykładowa implementacja usuwałaby z listy ciąg wejściowy za każdym razem, gdy readLine był dzwoniłem. Zebraĺ 'oby rĂłwnieĹź wszystkie wyjĹ" cia zapisane na listÄ ™ odpowiedzi. Pod koniec testu, jeśli wszystko poszło dobrze, wtedy wszystkie dane wejściowe zostały odczytane i można potwierdzić na wyjściu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-20 19:29:36
Naprawiłem problem z odczytem ze standardowego wejścia, aby symulować konsolę...
Mój problem polegał na tym, że chciałbym spróbować napisać w JUnit test konsoli, aby utworzyć określony obiekt...
Problem jest taki, jak mówisz: jak mogę napisać w Stdin z testu JUnit?
Potem na studiach uczę się o przekierowaniach, jak mówisz System.setIn (InputStream) Zmień stdin filedescriptor i możesz wtedy pisać...
Ale jest jeszcze jeden problem do naprawienia... blok testowy JUnit oczekiwanie na odczyt z nowego strumienia wejściowego, więc musisz utworzyć wątek do odczytu ze strumienia wejściowego i z JUnit test thread napisać w nowym Stdin... Najpierw musisz napisać na Stdin, ponieważ jeśli napiszesz później Utwórz wątek do przeczytania ze stdin, prawdopodobnie będziesz miał warunki wyścigowe... możesz pisać w strumieniu wejściowym przed odczytem lub możesz czytać ze strumienia wejściowego przed zapisem...To jest mój kod, moja znajomość angielskiego jest zła mam nadzieję, że wszystko rozumiesz problem i rozwiązanie do symulacji zapisu w stdin z testu JUnit.
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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-27 16:09:24