JUnit:如何模拟System.in测试?

zbsbpyhn  于 2022-11-11  发布在  其他
关注(0)|答案(9)|浏览(164)

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

bqjvbblv

bqjvbblv1#

从技术上讲,可以切换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);
}
wlwcrazw

wlwcrazw2#

基于@McDowell的答案和another answer that shows how to test 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()时,它将按照helper方法出现的顺序调用它们:

  1. setUpOutput(),因为@Before注解
  2. provideInput(String data),从testCase1()调用
  3. getOutput(),从testCase1()调用
  4. restoreSystemInputOutput(),因为@After注解
    我没有测试System.err,因为我不需要它,但是它应该很容易实现,类似于测试System.out
zpgglvta

zpgglvta3#

有几种方法可以实现这一点。最完整的方法是在运行被测类的同时传入一个InputStream,它是一个假的InputStream,将模拟数据传递给你的类。如果你需要在代码中经常这样做,你可以考虑依赖注入框架(比如Google Guice),但最简单的方法是:

public class MyClass {
     private InputStream systemIn;

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

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

在测试中,你会调用接受输入流的构造函数,你甚至可以将构造函数包私有化,并将测试放在同一个包中,这样其他代码通常就不会考虑使用它。

5q4ezhmt

5q4ezhmt4#

尝试重构你的代码以使用dependency injection。不要让你的a方法直接使用System.in,而是让该方法接受一个InputStream作为参数。然后在junit测试中,你将能够通过一个测试InputStream实现来代替System.in

r7xajy2e

r7xajy2e5#

您可以使用System Rules库的TextFromStandardInputStream规则为命令行界面编写一个clear测试。

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());
  }
}

充分披露:我是那个图书馆的作者。

uqxowvwt

uqxowvwt6#

您可以创建一个自定义的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 );

虽然我认为你应该更好地测试你的类应该如何处理从输入流中读取的数据,而不是测试它如何从那里读取数据。

f0brbegy

f0brbegy7#

BufferedReader.readLine()的问题在于它是一个等待用户输入的阻塞方法。在我看来,你并不特别想模拟它(即你希望测试更快)。但在测试上下文中,它在测试过程中不断地以高速返回null,这是令人讨厌的。
对于一个纯粹主义者来说,你可以把下面的getInputLine设置为package-private,然后嘲笑它:轻松的

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

......你必须确保你有办法停止(通常)用户与应用程序的交互循环。你还必须科普这样一个事实,即你的“输入行”总是相同的,直到你以某种方式改变了你的mock的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”类中需要输入行的方法之前。

rdlzhqv9

rdlzhqv98#

可能是这样的(未测试):

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

w41d8nur9#

@斯特凡·伯克纳,谢谢!
1.修改Pom.xml

Ref:
https://stackoverflow.com/a/66127606/8317677
https://github.com/stefanbirkner/system-lambda/blob/master/pom.xml
https://github.com/stefanbirkner/system-lambda/blob/master/src/test/java/com/github/stefanbirkner/systemlambda/WithTextFromSystemInTest.java

  <properties>
      <system-lambda.version>1.2.1</system-lambda.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>com.github.stefanbirkner</groupId>
      <artifactId>system-lambda</artifactId>
      <version>${system-lambda.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

1.添加功能代码

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class SimpleProgram003 {
    public static void main(String[] args) {
        try{
            String c;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            do{
                c = in.readLine();
                System.out.println(c);
                String d = c;
            }while(!c.equals("q"));
        }catch(Exception e){
            System.out.println("catch Exception");
        }
    }
}

1.添加测试代码

import static com.github.stefanbirkner.systemlambda.SystemLambda.*;
import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Unit test for simple App. JUnit 4.x.
 */
public class SimpleProgram003Test {
    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 setInput(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 1\nq\n";
        setInput(testString);

        SimpleProgram003.main(new String[0]);
        // String a = getOutput();
        assertEquals("Hello 1\r\nq\r\n", getOutput());
    }

    @Test // Multiply inputs
    public void testCase2() throws Exception {
        withTextFromSystemIn(
            "Input1",
            "Input2",
            "q",
            "Input3"
        ).execute(() -> {
            SimpleProgram003.main(new String[0]);
            // String a = getOutput();
            assertEquals("Input1\r\nInput2\r\nq\r\n", getOutput());
        });
    }
}

相关问题