Тестирование консольных приложений / программ - 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?

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

5 ответов

Решение

Почему бы не написать заявление, чтобы принять 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");

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

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

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

}

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

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

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

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

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

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

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

Переместить ваш println а также readLine вызовы других методов и использования вокруг nameConsumer, но не в ваших юнит-тестах.

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

Сохраняйте это простым, это окупается.

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

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

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

За подробностями обращайтесь к документации по системным правилам.

Другие вопросы по тегам