Тестирование 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 57

7 ответов:

вы можете заменить System.in с вами собственный поток, вызывая систему.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.io.Console, а затем использовать его для чтения или записи в систему.из. Реальная реализация будет делегировать в систему.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();      
}