【问题标题】:How might I test that a CLI app waits for input我如何测试 CLI 应用程序是否等待输入
【发布时间】:2016-10-22 16:23:36
【问题描述】:

这和这个问题不一样:JUnit: How to simulate System.in testing?,是关于模拟标准输入的。

我想知道的是如何测试(如在 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 设置为其他:

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

...这没有通过测试。

【问题讨论】:

  • 如果它确实等待输入,您的测试将永远等待。另外,你真的认为这是一个很好的测试吗?它不是在测试您的代码,而是在测试BufferedReader
  • 如果你真的想测试BufferedReader,那么你链接的帖子就是你需要的。请记住,TDD 或任何测试的想法不是测试由他人开发(并希望测试)的代码。测试readLine() 是否阻塞直到它收到\n 是非常荒谬的。
  • (稍作修改以显示我正在尝试做的事情)。我不认为这是关于测试BufferedReader。没有br.readLine() 行的main 方法会立即返回:所以我需要一个测试来证明包含该行的合理性。如果该行应用程序代码随后因某种原因被删除或未执行,我希望此测试失败。一个简单的事实是br.readLine() 在从我的测试方法运行应用程序类时绝对不会阻塞。为什么不呢?
  • 因为System.in 不是您在正常运行 CLI 程序时所期望的标准输入。我想它是空的。
  • 感谢您一直以来的关注...不过,我认为这不太正确:请参阅我添加的代码:我已尝试将 stdin 设置为其他...

标签: java unit-testing stdin


【解决方案1】:

作为更一般的规则,静态方法(例如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));
}

关键思想是,当您“真正”运行应用程序时,即通过main 方法,它将使用标准输入和输出流。但是,当您从测试中运行它时,它会使用纯内存输入/输出流,您可以在测试中完全控制它。 ByteArrayOutputStream 只是一个示例,但您可以在我的示例中看到该测试能够检查已写入输出流的实际字节。

【讨论】:

  • 你的回答教会了我很多……虽然最后我认为我已经得出结论,System.in(包装在 BufferedReader 中)的这种阻塞 1)已经过广泛测试,必须信任和 2) 实际上不能在测试代码中进行测试,除非您使用 System.in 本身。我是一个苦苦挣扎的 TDD 新手... :)
  • 你已经搞定了。它无法在单元测试中进行测试,但您可以编写更大规模的“验收测试”,它实际上将在不同的进程中运行您的程序并通过 std in 和 std err 与其交互, 但这会很麻烦,一般来说,与纯单元测试相比,这种风格的测试应该很少。
【解决方案2】:

一般来说:您无法测试您的程序是否正在等待直到某事发生(因为您无法测试它是否永远等待)

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-26
    • 1970-01-01
    • 2012-09-08
    相关资源
    最近更新 更多