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.
Author: Wimpey, 2011-06-20

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;
}
 79
Author: KrzyH,
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ć.

 15
Author: Garrett Hall,
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.

 5
Author: Jeff Bowman,
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 !

 2
Author: Omar Elshal,
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.

 1
Author: Sarah Haskins,
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.

 1
Author: massfords,
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();      
}
 1
Author: Javier Gutiérrez-Maturana Sánc,
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