Тестирование JUnit с имитацией пользовательского ввода

Я пытаюсь создать некоторые тесты JUnit для метода, который требует ввода пользователем. Тестируемый метод выглядит примерно так:

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;
}

есть ли возможный способ автоматически передать программе int вместо меня или кого-то другого, делающего это вручную в методе теста JUnit? Как имитация пользовательского ввода?

спасибо заранее.

7 ответов


вы можете заменить Система.в с вами собственный поток, вызывая систему.setIn (InputStream in). Входной поток может быть массивом байтов:

ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);

// do your thing

// optionally, reset System.in to its original
System.setIn(System.in)

другой подход может сделать этот метод более проверяемым, передавая и выходя в качестве параметров:

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;
}

чтобы протестировать ваш код, вы должны создать оболочку для системных функций ввода / вывода. Вы можете сделать это с помощью инъекции зависимостей, давая нам класс, который может запрашивать новые целые числа:

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();
    }
}

затем вы можете создать тесты для своей функции, используя макет фреймворка (я использую 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.");
}

затем напишите функцию, которая проходит тесты. Функция намного чище, так как вы можете удалить целочисленное дублирование запроса/получения и фактические системные вызовы инкапсулированный.

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;
}

Это может показаться излишним для вашего небольшого примера, но если вы создаете более крупное приложение, развивающееся таким образом, может окупиться довольно быстро.


одним из распространенных способов проверки аналогичного кода было бы извлечь метод, который принимает сканер и PrintWriter, подобный этот ответ StackOverflow и проверить, что:

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;
}

обратите внимание, что вы не сможете прочитать свой вывод до конца, и вам придется указать все ваши входные данные:

@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.")););
}

конечно, вместо создания метода перегрузки вы также можете сохранить "сканер" и "вывод" как изменяемые поля в вашей системе испытываемый. Мне нравится держать классы как можно более безгосударственными, но это не очень большая уступка, если это имеет значение для вас или ваших коллег/инструктора.

вы также можете поместить свой тестовый код в тот же пакет Java, что и тестируемый код (даже если он находится в другой исходной папке), что позволяет вам ослабить видимость перегрузки двух параметров, чтобы быть частным пакетом.


мне удалось найти более простой способ. Однако вы должны использовать внешнюю библиотеку


вы можете начать с извлечения логики, которая извлекает номер с клавиатуры в свой собственный метод. Затем можно проверить логику проверки не беспокоясь о клавиатуре. Для того, чтобы проверить клавиатуру.вызов nextInt () вы можете рассмотреть возможность использования макета объекта.


Я нашел полезным создать интерфейс, который определяет методы, подобные java.Ио.Консоль, а затем использовать его для чтения или записи в систему.из. Реальная реализация будет делегирована системе.console () в то время как Ваша версия JUnit может быть макет объекта с консервированным ввода и ожидаемых ответов.

например, вы построили бы MockConsole, который содержал бы консервированный ввод от пользователя. Макет реализации будет всплывать входную строку из списка каждый раз, когда readLine был вызван. Он также соберет все результаты, записанные в список ответов. В конце теста, если все прошло хорошо, то все ваши входные данные были бы прочитаны, и вы можете утверждать на выходе.


я исправил проблему чтения из stdin для имитации консоли...

мои проблемы были в том, что я хотел бы попробовать написать в JUnit тест консоли для создания определенного объекта...

проблема похожа на все, что вы говорите: как я могу написать в Stdin из теста JUnit?

затем в колледже я узнаю о перенаправлениях, как вы говорите, система.setIn (InputStream) измените stdin filedescriptor, и вы можете записать его...

но есть еще одна проблема в устанавливать... тестовый блок JUnit, ожидающий чтения из нового InputStream, поэтому вам нужно создать поток для чтения из InputStream и из тестового потока JUnit в новом Stdin... Сначала вам нужно написать в Stdin, потому что если вы напишете позже о создании потока для чтения из stdin, у вас, вероятно, будут условия гонки... вы можете писать в InputStream перед чтением или вы можете читать из InputStream перед записью...

Это мой код, мой английский плохо, я надеюсь, все вы можете понять проблему и решение для имитации записи в stdin из теста 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();      
}