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

Nop*_*nit 48 java junit command-line

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

谢谢

McD*_*ell 61

切换技术上是可行的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);
}
Run Code Online (Sandbox Code Playgroud)


Ant*_*dei 12

根据@ 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

要测试它,我们可以使用以下类:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

我不会解释太多,因为我相信代码是可读的,我引用了我的来源.

当JUnit运行时testCase1(),它将按照它们出现的顺序调用辅助方法:

  1. setUpOutput(),因为@Before注释
  2. provideInput(String data),来自 testCase1()
  3. getOutput(),来自 testCase1()
  4. restoreSystemInputOutput(),因为@After注释

我没有测试,System.err因为我不需要它,但它应该很容易实现,类似于测试System.out.

  • 您将如何继续使用多个输入? (3认同)
  • 对于我所需要的,将带有换行符的 `String` 传递给 `provideInput(String data)` 就足够了。对于我在命令行界面上运行的程序,每次用户输入一些文本并点击“Enter”时,该文本都会被视为新输入。请注意,`SimpleProgram` 使用 Java SE [`Scanner`](https://docs.oracle.com/javase/6/docs/api/java/util/Scanner.html) 类来读取用户的输入。 (2认同)
  • 所以,我可以调用`provideInput("输入1 \n输入2 \n输入3");而不是. (2认同)

Yis*_*hai 8

有几种方法可以解决这个问题.最完整的方法是在运行被测试类时传入一个InputStream,这是一个伪造的InputStream,它将模拟数据传递给您的类.如果您需要在代码中执行此操作,可以查看依赖注入框架(例如Google Guice),但简单的方法是:

 public class MyClass {
     private InputStream systemIn;

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

     public MyClass(InputStream in) {
         systemIn = in;
     }
 }
Run Code Online (Sandbox Code Playgroud)

在测试中,您将调用获取输入流的构造函数.您甚至将该构造函数包设为私有并将测试放在同一个包中,以便其他代码通常不会考虑使用它.

  • +1.我和你在一起.我会更进一步:`InputData`作为单元测试中`InputStream`的高级包装,你应该更关心你的类做什么,而不是关于集成. (3认同)

Asa*_*aph 5

尝试重构代码以使用依赖注入。不要让您的方法System.in直接使用,而是让方法接受一个InputStream作为参数。然后,在junit测试中,您可以通过测试InputStream实现来代替System.in