Статьи

Тестирование System.in и System.out с системными правилами

Написание модульных тестов является неотъемлемой частью разработки программного обеспечения. Одна проблема, которую вы должны решить, когда тестируемый класс взаимодействует с операционной системой, — это моделировать ее поведение. Это можно сделать с помощью имитаций вместо реальных объектов, предоставляемых Java Runtime Environment (JRE). Библиотеки, которые поддерживают mocking для Java, являются, например, mockito или jMock .

Насмешка над объектами — это замечательно, когда вы полностью контролируете их создание. При работе со стандартным вводом и стандартным выводом это немного сложно, но не невозможно, так как java.lang.System позволяет заменить стандартные InputStream и OutputStream .

1
2
System.setIn(in);
System.setOut(out);

Чтобы вам не приходилось заменять потоки до и после каждого теста вручную, вы можете использовать org.junit.rules.ExternalResource . Этот класс предоставляет два метода before() и after() , которые вызываются, как и предполагают их имена, до и после каждого теста. Таким образом, вы можете легко настроить и очистить ресурсы, необходимые для всех ваших тестов в рамках одного класса. Или, чтобы вернуться к исходной проблеме, замените поток ввода и вывода для java.lang.System .

Именно то, что я описал выше, реализуется библиотекой system-rules . Чтобы увидеть, как это работает, давайте начнем с простого примера:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class CliExample {
    private Scanner scanner = new Scanner(System.in, "UTF-8");
  
    public static void main(String[] args) {
        CliExample cliExample = new CliExample();
        cliExample.run();
    }
  
    private void run() {
        try {
            int a = readInNumber();
            int b = readInNumber();
            int sum = a + b;
            System.out.println(sum);
        } catch (InputMismatchException e) {
            System.err.println("The input is not a valid integer.");
        } catch (IOException e) {
            System.err.println("An input/output error occurred: " + e.getMessage());
        }
    }
  
    private int readInNumber() throws IOException {
        System.out.println("Please enter a number:");
        String nextInput = scanner.next();
        try {
            return Integer.valueOf(nextInput);
        } catch(Exception e) {
            throw new InputMismatchException();
        }
    }
}

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

В первом тестовом примере мы хотим убедиться, что программа правильно суммирует два числа и выводит результат:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class CliExampleTest {
    @Rule
    public final StandardErrorStreamLog stdErrLog = new StandardErrorStreamLog();
    @Rule
    public final StandardOutputStreamLog stdOutLog = new StandardOutputStreamLog();
    @Rule
    public final TextFromStandardInputStream systemInMock = emptyStandardInputStream();
  
    @Test
    public void testSuccessfulExecution() {
        systemInMock.provideText("2\n3\n");
        CliExample.main(new String[]{});
        assertThat(stdOutLog.getLog(), is("Please enter a number:\r\nPlease enter a number:\r\n5\r\n"));
    }
    ...
}

Для моделирования System.in мы используем системные правила TextFromStandardInputStream . Переменная экземпляра инициализируется с пустым входным потоком путем вызова emptyStandardInputStream() . В самом тестовом примере мы предоставляем intput для приложения, вызывая provideText() с новой provideText() в соответствующих точках. Затем мы вызываем метод main() нашего приложения. Наконец, мы должны утверждать, что приложение записало два оператора ввода и результат в стандартный ввод. Последнее делается через экземпляр StandardOutputStreamLog . getLog() его метод getLog() мы извлекаем все, что было записано в стандартный вывод во время текущего теста.

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

1
2
3
4
5
6
@Test
public void testInvalidInput() throws IOException {
    systemInMock.provideText("a\n");
    CliExample.main(new String[]{});
    assertThat(stdErrLog.getLog(), is("The input is not a valid integer.\r\n"));
}

Помимо этого system-rules также предлагает правила для работы с System.getProperty() , System.setProperty() , System.exit() и System.getSecurityManager() .

Вывод : С тестированием системных правил приложения командной строки с модульными тестами становятся еще проще, чем использование самих правил junit. Весь стандартный код для обновления системной среды до и после каждого контрольного примера входит в ряд простых в использовании правил.

PS: Вы можете найти полные источники здесь .