我如何测试CLI应用程序是否等待输入

时间:2016-09-22 19:43:01

标签: java unit-testing stdin

这与这个问题不同:JUnit: How to simulate System.in testing?,这是关于模仿stdin。

我想知道的是如何测试(如在TDD中)使用main方法的简单Java类等待输入。

我的测试:

@Test 
public void appRunShouldWaitForInput(){
    long startMillis = System.currentTimeMillis();
    // NB obviously you'd want to run this next line in a separate thread with some sort of timeout mechanism... 
    // that's an implementation detail I've omitted for the sake of avoiding clutter!
    App.main( null );
    long endMillis = System.currentTimeMillis();
    assertThat( endMillis - startMillis ).isGreaterThan( 1000L );
}

我的SUT main

public static void main(String args[]) {
    BufferedReader br = null;
    try {
        br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("Enter something : ");
        String input = br.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    } 

...测试失败。代码不等待。但是当你在命令提示符下运行应用程序时,确实会等待。

我的方式也尝试将stdin设置为sthg else:

System.setIn(new ByteArrayInputStream( dummy.getBytes()));
scanner = new Scanner(System.in);

......这没有阻碍测试。

2 个答案:

答案 0 :(得分:1)

作为更一般的规则,静态方法(例如'utf8' codec can't decode byte 0x88 in position 21: invalid start byte 方法)很难测试。因此,您几乎从不从测试代码中调用main方法(或任何其他静态方法)。解决这个问题的一个常见模式是转换它:

main

到此:

public class App {
    public static void main(String args[]) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("Enter something : ");
            String input = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

现在你的测试变成了这个:

public class App {
    private final InputStream input;
    private final OutputStream output;

    public App(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public static void main(String[] args) {
        new App(System.in, System.out).start();
    }

    public void start() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(input));
            output.print("Enter something : ");
            String nextInput = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

关键的想法是,当您通过@Test public void appRunShouldWaitForInput(){ ByteArrayOutputStream output = new ByteArrayOutputStream(); // As you have already noted, you would need to kick this off on another thread and use a blocking implementation of InputStream to test what you want to test. new App(new ByteArrayInputStream(), output).start(); assertThat(output.toByteArray().length, is(0)); } 方法“实现”运行应用程序时,它将使用标准输入和输出流。但是,当您从测试中运行它时,它使用纯粹的内存输入/输出流,您可以在测试中完全控制它。 ByteArrayOutputStream只是一个示例,但您可以在我的示例中看到测试能够检查已写入输出流的实际字节。

答案 1 :(得分:1)

一般情况下:你无法测试你的程序是否在等到事情发生(因为如果它永远等待你就无法测试)

在这种情况下通常会做什么:

  1. 在你的情况下:不要测试它。 readLine由经过相当密集测试的外部库提供。测试成本远高于此类测试的价值。重构并测试您的业务代码(字符串/流操作),而不是基础结构(系统输入)

  2. 在一般情况下,它测试并发编程。它很难,所以人们通常会尝试找到一些有用的简化:

    1. 您可以测试您的程序输出是否与测试中提供的输入相符。
    2. 对于一些真正困难的问题,上面的技术与运行测试相结合数千次(如果可能的话,强制进行线程切换)来检测并发编程中的错误(违反不变量)
    3. 在您的测试提供输入日期之前,您可以测试您的程序是否处于正确状态(等待线程,正确的字段值等)
    4. 在最坏的情况下,您可以在提供输入之前使用延迟测试,确保程序等待并使用输入。这种技术经常被使用,因为它很简单,但它很臭,如果你添加更多的测试,你的整个套装会变得更慢