JUnit:如何模拟System.intesting?
我有一个Java命令行程序。 我想创buildJUnittesting用例来模拟System.in
。 因为当我的程序运行时,它将进入while循环,并等待来自用户的input。 我如何在JUnit中模拟?
谢谢
在技术上切换System.in
是可能的,但通常情况下,不会直接在代码中调用它,而是添加一个间接层,以便从应用程序中的一个点控制input源。 实际上你是怎么做的,这是一个实现细节 – dependency injection的build议是好的,但你不一定需要引入第三方框架; 例如,您可以传递来自调用代码的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); }
有几种方法可以解决这个问题。 最完整的方法是在运行被testing的类时inputInputStream,这是一个假的InputStream,它将模拟数据传递给你的类。 如果您需要在代码中执行此操作,您可以查看dependency injection框架(如Google Guice),但最简单的方法是:
public class MyClass { private InputStream systemIn; public MyClass() { this(System.in); } public MyClass(InputStream in) { systemIn = in; } }
在testing中,您将调用接受inputstream的构造函数。 你甚至可以把这个构造函数包私人化,并把testing放在同一个包中,这样其他代码通常不会考虑使用它。
尝试重构你的代码来使用dependency injection 。 而不是直接使用System.in
的方法,让方法接受InputStream
作为参数。 然后在你的junittesting中,你将能够通过一个testingInputStream
实现来代替System.in
。
您可以使用System Rules库的TextFromStandardInputStream
规则为命令行界面编写明确的testing。
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()); } }
充分披露:我是该图书馆的作者。
您可以创build一个自定义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 );
虽然我认为你应该更好地testing你的类应该如何处理从inputstream中读取的数据,而不是从那里读取数据。
BufferedReader.readLine()
的问题在于它是一个等待用户input的阻塞方法。 在我看来,你并不特别想模拟(即你想要快速testing)。 但是在testing环境中,它在testing过程中不断地返回null
,这是令人厌烦的。
对于一个纯粹主义者,你可以使getInputLine
在package-private之下,并且模拟它:容易 – 偷看。
String getInputLine() throws Exception { return br.readLine(); }
…你必须确保你有一种停止(通常)与应用程序的用户交互循环的方式。 你还必须应对这样一个事实,即你的“input路线”总是相同的,直到你改变了你的模拟的结果:几乎没有用户的input。
对于一个非纯粹主义者而言,他们希望让自己的生活变得简单(并且产生可读的testing),所有这些东西都可以放在你的应用代码中:
private Deque<String> inputLinesDeque; void setInputLines(List<String> inputLines) { inputLinesDeque = new ArrayDeque<String>(inputLines); } private String getInputLine() throws Exception { if (inputLinesDeque == null) { // ... ie 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; }
…在你的testing中,你可能会这样做:
consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));
在触发这个需要input行的“ConsoleHandler”类中的方法之前。
也许这样(未testing):
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);