Тестирование JUnit с имитацией пользовательского ввода - PullRequest
67 голосов
/ 20 июня 2011

Я пытаюсь создать несколько тестов 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 ]

87 голосов
/ 20 июня 2011

Вы можете заменить System.in собственным потоком, вызвав System.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)

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

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;
}
16 голосов
/ 20 июня 2011

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

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

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

5 голосов
/ 29 апреля 2014

Один из распространенных способов проверки подобного кода - извлечь метод, который использует Scanner и 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, что и тестируемый код (даже если он находится в другой папке с исходным кодом), что позволяет ослабить видимость перегрузки двух параметров как частной для пакета .

2 голосов
/ 15 ноября 2016

Мне удалось найти более простой способ. Однако вы должны использовать внешнюю библиотеку System.rules By @Stefan Birkner

Я просто взял приведенный там пример, я думаю, что он не мог бы быть проще:)

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

Проблема, однако, в моем случае у моего второго ввода есть пробелы, и это делает весь поток ввода пустым!

2 голосов
/ 20 июня 2011

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

1 голос
/ 26 марта 2017

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

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

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

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

Но есть еще одна проблема, которую нужно исправить ... тестовый блок 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();      
}
1 голос
/ 20 июня 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...