Подтвердить что ты не робот

JUnit: Как имитировать тестирование System.in?

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

Спасибо

4b9b3361

Ответ 1

Технически возможно переключить System.in, но в целом было бы более надежно не называть его прямо в вашем коде, а добавлять слой косвенности, поэтому источник ввода управляется с одной точки вашего приложения. Точно так же, как вы это делаете, это детализация реализации - предложения по вложению инъекций в порядке, но вам необязательно вводить сторонние структуры; вы можете, например, обойти контекст ввода-вывода из вызывающего кода.

Как переключить System.in:

String data = "Hello, World!\r\n";
InputStream stdin = System.in;
try {
  System.setIn(new ByteArrayInputStream(data.getBytes()));
  Scanner scanner = new Scanner(System.in);
  System.out.println(scanner.nextLine());
} finally {
  System.setIn(stdin);
}

Ответ 2

Есть несколько способов приблизиться к этому. Наиболее полным способом является переход в InputStream при запуске тестируемого класса, который является поддельным InputStream, который передает смоделированные данные в ваш класс. Вы можете взглянуть на инфраструктуру инъекций зависимостей (например, Google Guice), если вам нужно сделать это очень много в своем коде, но простой способ:

 public class MyClass {
     private InputStream systemIn;

     public MyClass() {
         this(System.in);
     }

     public MyClass(InputStream in) {
         systemIn = in;
     }
 }

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

Ответ 3

Попробуйте реорганизовать свой код, чтобы использовать инъекцию зависимостей. Вместо того, чтобы иметь метод, который использует System.in напрямую, попросите метод принять InputStream в качестве аргумента. Затем в вашем тесте junit вы сможете передать тестовую реализацию InputStream вместо System.in.

Ответ 4

Вы можете написать четкий тест для интерфейса командной строки, используя правило TextFromStandardInputStream библиотеки Системные правила.

public void MyTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void readTextFromStandardInputStream() {
    systemInMock.provideLines("foo");
    Scanner scanner = new Scanner(System.in);
    assertEquals("foo", scanner.nextLine());
  }
}

Полное раскрытие: я являюсь автором этой библиотеки.

Ответ 5

Основываясь на ответе @McDowell и другом ответе, который показывает, как тестировать System.out, я хотел бы поделиться своим решением, чтобы дать вход в программу и протестировать ее выход.

В качестве ссылки я использую JUnit 4.12.

Скажем, у нас есть эта программа, которая просто реплицирует входные данные:

import java.util.Scanner;

public class SimpleProgram {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print(scanner.next());
        scanner.close();
    }
}

Чтобы проверить его, мы можем использовать следующий класс:

import static org.junit.Assert.*;

import java.io.*;

import org.junit.*;

public class SimpleProgramTest {
    private final InputStream systemIn = System.in;
    private final PrintStream systemOut = System.out;

    private ByteArrayInputStream testIn;
    private ByteArrayOutputStream testOut;

    @Before
    public void setUpOutput() {
        testOut = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOut));
    }

    private void provideInput(String data) {
        testIn = new ByteArrayInputStream(data.getBytes());
        System.setIn(testIn);
    }

    private String getOutput() {
        return testOut.toString();
    }

    @After
    public void restoreSystemInputOutput() {
        System.setIn(systemIn);
        System.setOut(systemOut);
    }

    @Test
    public void testCase1() {
        final String testString = "Hello!";
        provideInput(testString);

        SimpleProgram.main(new String[0]);

        assertEquals(testString, getOutput());
    }
}

Я не буду много объяснять, потому что я считаю, что код читаем, и я привел свои источники.

Когда JUnit запускает testCase1(), он будет вызывать вспомогательные методы в следующем порядке:

  1. setUpOutput(), из-за аннотации @Before
  2. provideInput(String data), вызываемые из testCase1()
  3. getOutput(), вызываемый из testCase1()
  4. restoreSystemInputOutput(), из-за @After аннотации

Я не тестировал System.err потому что мне это не нужно, но его должно быть легко реализовать, как и при тестировании System.out.

Ответ 6

Вы можете создать пользовательский InputStream и прикрепить его к классу System

class FakeInputStream extends InputStream {

    public int read() {
         return -1;
    }
}

И затем используйте его со своим Scanner

System.in = new FakeInputStream();удаp >

До:

InputStream in = System.in;
...
Scanner scanner = new Scanner( in );

После:

InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );

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

Ответ 7

Проблема с BufferedReader.readLine() заключается в том, что это метод блокировки, который ждет ввода пользователя. Мне кажется, что вы не особенно хотите имитировать это (т.е. Вы хотите, чтобы тесты были быстрыми). Но в тестовом контексте он постоянно возвращает null на высокой скорости во время тестирования, что очень неприятно.

Для пуриста вы можете сделать getInputLine ниже package-private и издеваться над ним: легко peezy.

String getInputLine() throws Exception {
    return br.readLine();
}

... вам нужно убедиться, что у вас есть способ остановить (как правило) цикл взаимодействия пользователя с приложением. Вам также придется справляться с тем, что ваши "входные строки" всегда будут одинаковыми, пока вы каким-то образом не измените doReturn вашего макета: вряд ли типично для ввода пользователя.

Для непуриста, который хочет сделать жизнь легкой для себя (и подготовить читаемые тесты), вы можете поместить все это в свой код приложения:

private Deque<String> inputLinesDeque;

void setInputLines(List<String> inputLines) {
    inputLinesDeque = new ArrayDeque<String>(inputLines);
}

private String getInputLine() throws Exception {
    if (inputLinesDeque == null) {
        // ... i.e. normal case, during app run: this is then a blocking method
        return br.readLine();
    }
    String nextLine = null;
    try {
        nextLine = inputLinesDeque.pop();
    } catch (NoSuchElementException e) {
        // when the Deque runs dry the line returned is a "poison pill", 
        // signalling to the caller method that the input is finished
        return "q";
    }

    return nextLine;
}

... в вашем тесте вы можете пойти следующим образом:

consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));

прежде чем запускать метод в этом классе ConsoleHandler, который нуждается в строках ввода.

Ответ 8

может быть так (не проверено):

InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));

in.write("text".getBytes("utf-8"));

System.setIn( save_in );

больше частей:

//PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));

InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));

//start something that reads stdin probably in a new thread
//  Thread thread=new Thread(new Runnable() {
//      @Override
//      public void run() {
//          CoursesApiApp.main(new String[]{});                 
//      }
//  });
//  thread.start();


//maybe wait or read the output
//  for(int limit=0; limit<60 && not_ready ; limit++)
//  {
//      try {
//          Thread.sleep(100);
//      } catch (InterruptedException e) {
//          e.printStackTrace();
//      }
//  }


in.write("text".getBytes("utf-8"));

System.setIn( save_in );

//System.setOut(save_out);