【问题标题】:How to unit test a void method with no arguments如何对没有参数的 void 方法进行单元测试
【发布时间】:2015-07-29 20:55:34
【问题描述】:

我需要测试一个类以确定在给定特定输入的情况下是否发生了相关操作。代码是这样的:

protected static void receiveInput() {
    String command;
    boolean b = true;
    Scanner scanner = new Scanner(System.in);

    while (b) {

        command = scanner.next();
        switch(command) {

            case "first":
                System.out.println("First!");
                break;

            case "second":
                System.out.println("Second!");
                break;

            case "third":
                System.out.println("Third!");
                break;

            default:
                System.out.println("\n***** Invalid *****\n"); 
        }

        System.out.println("\n");
    }

    scanner.close();
}

如果我能以某种方式控制命令字符串并跟踪扫描仪对象,我可能会得到一个相当彻底的单元测试。出于某种原因,我实际上不允许将这两个对象作为此方法的参数注入。有哪些选择可以以不同的方式进行此测试?

【问题讨论】:

  • 您不能将其设为string 类型的值返回函数,而只返回特定的大小写字符串以进行检查?
  • 您能否更改此方法以使其对单元测试更友好?
  • 这不是一个对单元测试非常友好的方法。通常对我来说,当我对 void 方法进行单元测试时,这是因为它们会产生某种副作用或状态变化。然后我测试该副作用是否正确发生。不幸的是,在这种情况下,如果不更改方法就很难进行单元测试。

标签: java unit-testing junit switch-statement mockito


【解决方案1】:

这是使函数更可测试的完美示例。此函数不接受输入也不产生输出,仅通过副作用影响世界,使其完全无法通过标准方式进行测试。

重构此函数以使其更具可测试性:

  • 不要通过扫描器要求用户输入,将用户的输入作为输入参数。
  • 不打印结果,返回它。

然后,函数的调用者可以决定如何提供输入以及如何处理输出。修改后的程序可以要求用户输入,调用函数,然后打印输出。测试框架可以从输入列表中提供输入进行测试,并根据预期值检查输出。

【讨论】:

  • 您可以将“业务逻辑”包装在一个可测试的方法中,该方法接受输入并返回输出,从而保持您的 void 方法完好无损。可能更理想的方法是让该方法接受两个参数,InputStream(或 Reader)和 OutputStream(或 Writer,或 PrintWriter)
  • 您提到了标准方法。这是否意味着还有其他方法可以进行单元测试?这正是我想要的。
  • 要按原样进行测试,您必须截取输入和输出流。可以做到,但是太丑了。
  • 很好的答案!有一本很棒的书,无论经验如何,我都向大家推荐... Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin...
  • 假设我要尝试测试 I/O。我将如何以捕获函数的方式编写它?
【解决方案2】:

除了 MattPutnam 的出色回答之外,还有两个针对您的方法的具体提示。

1.改为返回字符串

protected static string receiveInput(String arg) {
    String result;
    String command = arg;
    boolean b = true;

    while (b) {
        switch(command) {

            case "first":
                result = "first";
                break;

            case "second":
                result = "second";
                break;

            case "third":
                result = "third";
                break;

            default:
                result = "\n ****Invalid*** \n"; 
        }
    }
    return result;
}

2.我的偏好只是测试boolean b 是否为真。如果是真的,你知道它有效,如果不是,它没有。 编辑:请注意,这可能很危险,因为您不会检查 arg 是否一定符合字符串标准,而是检查它是否有可接受的答案。

关于您要测试此当前方法的唯一方法是以某种方式捕获流,因为 MattPutnam 在 cmets 中回答。

【讨论】:

  • 危险只是为了证明布尔值......我建议将整个逻辑提取到一个自己的方法中,正如 MattPutman 所说......
  • 这就是我将其列在第 1 位之后的原因。我明白了您为什么说现在我阅读了我的答案可能很危险。我会编辑。
  • @MilesPeterson,我强烈建议你接受 MattPutnam 的回答
【解决方案3】:

您可以使用http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#setIn(java.io.InputStream) 修改 System.in 并从文件或 ByteArrayInputStream 中读取。

同理,使用http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#setOut(java.io.PrintStream)重定向输出流。

【讨论】:

    【解决方案4】:

    您可以使用 System Rules 库测试输出。

    public class YourTest {
      @Rule
      public SystemOutRule log = new SystemOutRule().enableLog();
      @Rule
      public TextFromStandardInputStream systemInMock = emptyStandardInputStream();
    
      @Test
      public void test() {
        systemInMock.provideLines("first", "second");
        ... //execute your code
        assertEquals("First!\nSecond!\n", log.getLogWithNormalizedLineSeparator());
      }
    }
    

    完全披露:我是系统规则的作者。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-10
      • 1970-01-01
      • 2014-02-08
      • 1970-01-01
      • 2011-06-25
      • 2016-01-19
      • 2015-08-20
      相关资源
      最近更新 更多