JUnit的:如何模拟System.in测试?(JUnit: How to simulate Sys

2019-09-02 11:32发布

我有一个Java命令行程序。 我想创建JUnit测试案例能够模拟System.in 。 因为当我的程序运行时它会进入while循环,并等待来自用户的输入。 如何模拟在JUnit的?

谢谢

Answer 1:

这在技术上是可以切换System.in ,但在一般情况下,它会更强劲不要直接在代码中调用它,但这样的输入源从应用程序中的一个点控制添加了一个间接层。 究竟如何做到这一点是一个实现细节 - 依赖注入的建议都很好,但你不一定需要引入第三方框架; 你可以通过轮从调用代码的I / O方面,例如。

如何切换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);
}


Answer 2:

根据@麦克道尔的回答和另一种答案,说明如何进行测试的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



Answer 3:

有几种方法可以解决这个。 最完整的方式是在一个InputStream通过在运行测试中的类,它是通过模拟数据类假的InputStream。 你可以看一下依赖注入框架(如谷歌吉斯)如果你需要做这个有很多在你的代码,但简单的方法是:

 public class MyClass {
     private InputStream systemIn;

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

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

根据测试,你会调用了一个输入流的构造。 你甚至云作出这样的构造函数包私有,并把测试在同一个包,让其他的代码通常不会考虑使用它。



Answer 4:

尝试重构你的代码中使用依赖注入 。 而不是你的使用方法的System.in直接,有法接受InputStream作为参数。 然后在你的JUnit测试,你就可以通过测试InputStream执行到位的System.in



Answer 5:

您可以通过使用写命令行界面清晰的测试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());
  }
}

披露:我是该库的作者。



Answer 6:

你可以创建一个自定义InputStream 并将其连接到System

class FakeInputStream extends InputStream {

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

然后你用它Scanner

System.in = new FakeInputStream();

之前:

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

后:

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

虽然我认为你应该更好地测试你的类应该如何工作,从输入流中读取数据,并没有真正如何从那里读取。



Answer 7:

这个问题BufferedReader.readLine()是,它是它等待用户输入的阻塞方法。 在我看来,你并不特别想以模拟(也就是说你想测试要快)。 但在测试范围内它会不断返回null测试,这是令人厌烦的过程中高速。

对于一个纯粹的,你可以使getInputLine下面包私人和嘲笑它:易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”级触发关闭方法。



Answer 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);


文章来源: JUnit: How to simulate System.in testing?