Тестирование консольных приложений/программ - Java

Все,

Я написал приложение PhoneBook на Java, основанное на командной строке. Приложение в основном запрашивает некоторые детали пользователя, такие как имя, возраст, адрес и номера телефонов, и сохраняет их в файле. Другие операции включают поиск телефонной книги по имени, номеру телефона и т.д. Все детали вводятся через консоль.

Я пытаюсь написать тестовые примеры JUnit для каждой из реализованных функций, но не могу понять, как перенаправить System.in в код реализации на что-то в моих методах тестирования JUnit, которые будут предоставлять эти значения, когда мои фактический код останавливается для ввода пользователем?

Пример:

Мой код реализации:

BufferedReader is = new BufferedReader (new InputStreamReader(System.in));
System.out.println("Please enter your name:");
String name = is.readLine();             // My test cases stop at this line. How can I pass command line values i.e. redirect System.in to my test based values?

Надеюсь, что это имеет смысл

Ответ 1

Почему бы не написать приложение для ввода Reader в качестве входа? Таким образом, вы можете легко заменить InputStreamReader(System.in) на FileReader(testFile)

public class Processor {
    void processInput(Reader r){ ... }
}

И затем два экземпляра:

Processor live = new Processor(new InputStreamReader(System.in));
Processor test = new Processor(new FileReader("C:/tmp/tests.txt");

Привыкание к кодированию интерфейса принесет огромные преимущества практически во всех аспектах ваших программ!

Отметим также, что a Reader - это идиоматический способ обработки ввода на основе символов в программах Java. InputStream следует зарезервировать для обработки исходного байтового уровня.

Ответ 3

Я предлагаю вам разделить код на три части:

  • Чтение ввода (например, name в вашем примере)
  • Сделайте то, что вам нужно сделать с этим входом
  • Распечатайте результаты

Вам не нужно проверять результаты ввода и печати чтения, как тот код Java, который уже протестирован людьми, пишущими Java.

Единственное, что вам нужно проверить, это то, что делает вы, что бы это ни было. Модульные тесты называются так, потому что они проверяют единицы кода изолированно. Вы не тестируете всю программу, вы проверяете небольшие части, которые являются автономными и имеют четко определенную функцию.

В модульных тестах вы не должны полагаться на операции ввода/вывода. Вы должны предоставлять входы и ожидаемые выходы непосредственно в unit test. Иногда удобно использовать операции чтения файлов для подачи ввода или вывода (например, если объем данных огромен), но, как правило, чем больше вы переходите на ввод/вывод в своих модульных тестах, тем сложнее они становятся и вы скорее всего, не будут выполнять единичные, но интеграционные тесты.

В вашем случае вы как-то используете name. Если это единственный параметр, тогда сделайте метод - позвоните ему nameConsumer -, который принимает это имя, что-то делает и возвращает его результат. В своих модульных тестах сделайте следующее:

@Test
public void testNameConsumer() {
    // Prepare inputs
    String name = "Jon";
    String result = nameConsumer(name);
    assertEquals("Doe", result);
}

Перемещайте вызовы println и readLine на другие методы и используйте nameConsumer, но не в своих модульных тестах.

Подробнее об этом читайте здесь:

Держите его простым, оно окупается.

Ответ 4

Библиотека Системные правила предоставляет правило TextFromStandardInputStream для имитации ввода в тестах JUnit.

public class YourAppTest {
  @Rule
  public TextFromStandardInputStream systemInMock = emptyStandardInputStream();

  @Test
  public void test() {
    systemInMock.provideText("name\nsomething else\n");
    YourApp.main();
    //assertSomething
  }
}

Подробнее см. Документация системных правил.

Ответ 5

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

Собственное класс:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;

public class TestableLoopingConsoleExample {

   public static final String INPUT_LINE_PREFIX = "> ";
   public static final String EXIT_COMMAND = "exit";
   public static final String RESPONSE_PLACEHOLDER = "...response goes here...";
   public static final String EXIT_RESPONSE = "Exiting.";

   public static void main(String[] cmdLineParams_ignored) throws IOException {
      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
      PrintStream out = new PrintStream(System.out);
      PrintStream err = new PrintStream(System.err);

      try {
         new TestableLoopingConsoleExample().main(cmdLineParams_ignored, in, out);
      } catch (Exception e) {  //For real use, catch only the exactly expected types
         err.println(e.toString());
      }
   }

... продолжение...

   public void main(String[] cmdLineParams_ignored, BufferedReader in, PrintStream out)
         throws IOException {

      System.out.println("Enter some text, or '" + EXIT_COMMAND + "' to quit");

      while (true) {

         out.print(INPUT_LINE_PREFIX);
         String input = in.readLine();
         out.println(input);

         if (input.length() == EXIT_COMMAND.length() &&
            input.toLowerCase().equals(EXIT_COMMAND)) {

            out.println(EXIT_RESPONSE);
            return;
         }

         out.println(RESPONSE_PLACEHOLDER);
      }
   }
}

Тест (JUnit4):

import static org.junit.Assert.assertEquals;
import static testableloopingconsoleapp.TestableLoopingConsoleExample.EXIT_COMMAND;
import static testableloopingconsoleapp.TestableLoopingConsoleExample.EXIT_RESPONSE;
import static testableloopingconsoleapp.TestableLoopingConsoleExample.INPUT_LINE_PREFIX;
import static testableloopingconsoleapp.TestableLoopingConsoleExample.RESPONSE_PLACEHOLDER; 

import org.junit.Before;
import org.junit.Test; 

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.StringReader; 

public class TestableLoopingConsoleExampleTest { 

  private final ByteArrayOutputStream out = new ByteArrayOutputStream();
  private final ByteArrayOutputStream err = new ByteArrayOutputStream(); 

  @Before
  public final void resetOutputStreams() {
     out.reset();
     err.reset();
  } 

... продолжение...

  @Test
  public void testableMain_validInputFromString_outputAsExpected() throws Exception {
     String line1 = "input line 1\n";
     String line2 = "input line 2\n";
     String line3 = "input line 3\n";
     String exitLine = EXIT_COMMAND + "\n"; 

     BufferedReader in = new BufferedReader(new StringReader(
         line1 + line2 + line3 + exitLine
     ));
     String expectedOutput =
         INPUT_LINE_PREFIX + line1 +
         RESPONSE_PLACEHOLDER + "\n" +
         INPUT_LINE_PREFIX + line2 +
         RESPONSE_PLACEHOLDER + "\n" +
         INPUT_LINE_PREFIX + line3 +
         RESPONSE_PLACEHOLDER + "\n" +
         INPUT_LINE_PREFIX + exitLine +
         EXIT_RESPONSE + "\n"; 

     String[] ignoredCommandLineParams = null; 

     new TestableLoopingConsoleExample().main(ignoredCommandLineParams, in, new PrintStream(out)); 

     assertEquals(expectedOutput, out.toString());
  } 

}