当前位置: 代码迷 >> java >> 使用 Scanner 对用户输入进行 junit 测试
  详细解决方案

使用 Scanner 对用户输入进行 junit 测试

热度:41   发布时间:2023-07-16 17:43:45.0

我必须在一个使用 Scanner 类接受输入的类中测试一个方法。

package com.math.calculator;

import java.util.Scanner;

public class InputOutput {

    public String getInput() {
        Scanner sc = new Scanner(System.in);
        return sc.nextLine();
    }
}

我想使用 JUnit 对其进行测试,但不确定如何进行。

我尝试使用以下代码,但它不起作用。

package com.math.calculator;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class InputOutputTest {

    @Test
    public void shouldTakeUserInput() {
        InputOutput inputOutput= new InputOutput();

        assertEquals("add 5", inputOutput.getInput());
    }
}

我也想用 Mockito 尝试一下(使用 mock... when ... thenReturn),但不知道该怎么做。

您可以使用System.setIn()方法更改System.in流。

尝试这个,

@Test
public void shouldTakeUserInput() {
    InputOutput inputOutput= new InputOutput();

    String input = "add 5";
    InputStream in = new ByteArrayInputStream(input.getBytes());
    System.setIn(in);

    assertEquals("add 5", inputOutput.getInput());
}

您刚刚修改了System.in字段。 System.in基本上是从console读取的InputStream (因此您在控制台中输入)。 但是你只是修改了它,让系统从提供的输入inputstream读取。 所以它不再从控制台读取,而是从提供的输入流读取。

您可以使用库的TextFromStandardInputStream规则为命令行界面编写清晰的测试。

public void MyTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void shouldTakeUserInput() {
    systemInMock.provideLines("add 5", "another line");
    InputOutput inputOutput = new InputOutput();
    assertEquals("add 5", inputOutput.getInput());
  }
}

除了,正如 Codebender 还提到的,考虑重构,以便getInput()成为对您编写的彻底getInput(Scanner)方法的单行调用,您可以通过创建自己的Scanner("your\\ntest\\ninput\\n") 还有许多其他方法可以注入您的扫描仪依赖项,例如制作一个您覆盖测试的字段,但是仅制作一个方法重载非常简单,并且在技术上为您提供了更大的灵活性(让您添加一个功能以从文件中读取输入,例如)。

一般来说,请记住设计易于测试,并且比低风险部分更重地测试高风险部分。 这意味着重构是一个很好的工具,并且测试getInput(Scanner)可能比测试getInput()重要得多,尤其是当您所做的不仅仅是调用nextLine()

我强烈建议不要创建模拟 Scanner:模拟您不拥有的类型不仅是不好的做法,而且 Scanner 代表了一个非常大的相互关联方法的 API,其中调用顺序很重要。 在 Mockito 中复制它意味着你要么在 Mockito 中创建一个大的假 Scanner 实现,要么模拟一个最小的实现,它只测试你所做的调用(如果你的实现发生变化,即使你的更改提供了正确的结果,也会中断)。 使用真正的 Scanner 并保存 Mockito 练习以用于外部服务调用或模拟您定义的小型但未编写的 API 的情况。

首先,我假设您测试的目的是验证用户输入是从扫描仪获取的,并且返回的值是扫描仪中输入的值。

您嘲笑不起作用的原因是因为您每次都在getInput()方法中创建实际的扫描仪对象。 因此,无论你做什么,你的 mockito 实例都不会被调用。 因此,使此类可测试的正确方法是识别该类的所有外部依赖项(在本例中为java.util.Scanner并通过构造函数将它们注入类中。这样您就可以在期间注入模拟 Scanner 实例测试。这是实现依赖注入的基本步骤,这反过来又会导致良好的 TDD。一个例子可以帮助你:

 package com.math.calculator;

    import java.util.Scanner;

    public class InputOutput {

        private final Scanner scanner;

        public InputOutput()
        {
           //the external exposed default constructor 
           //would use constructor-chaining to pass an instance of Scanner.

           this(new Scanner(System.in));
        }

        //declare a package level constructor that would be visible only to the test class. 
      //It is a good practice to have a class and it's test within the same     package.
        InputOutput(Scanner scanner)
        {
            this.scanner  = scanner;
        }

        public String getInput() {

            return scanner.nextLine();
        }
    }

现在你的测试方法:

@Test
public void shouldTakeUserInput() {
    //create a mock scanner
    Scanner mockScanner = mock(Scanner.class);
    //set up the scanner
    when(mockScanner.nextLine()).thenReturn("add 5");

    InputOutput inputOutput= new InputOutput(mockScanner);

    //assert output
    assertEquals("add 5", inputOutput.getInput());

   //added bonus - you can verify that your scanner's nextline() method is
   //actually called See Mockito.verify
   verify(mockScanner).nextLine();
}

另请注意,由于在上述类中我使用构造函数进行注入,因此我已将 Scanner 实例声明为 final。 由于我在这个类中没有更多的可变状态,这个类是线程安全的。

基于构造函数的依赖注入的概念非常酷,值得在互联网上阅读。 它有助于开发良好的线程安全可测试代码。

  相关解决方案