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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31635698/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:20:48  来源:igfitidea点击:

junit testing for user input using Scanner

javajunitmockingmockito

提问by Ross

I have to test a method in a class which takes an input using Scanner class.

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

package com.math.calculator;

import java.util.Scanner;

public class InputOutput {

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

I want to test it using JUnit but not sure how to do it.

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

I tried using the following code but it wont work.

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

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());
    }
}

I want to also try it with Mockito (using mock... when ... thenReturn) but not sure how to do it.

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

回答by Codebender

You can change the System.instream using System.setIn()method.

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

Try this,

尝试这个,

@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());
}

You have just modified the System.infield. System.inis basically an InputStreamwhich reads from the console(hence your input in the console). But you just modified it and let the system to read from the provided inputstreaminstead. So it wont read from console anymore but from the inputstream provided.

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

回答by Jeff Bowman

In addition to switching System.in, as Codebender also mentioned, consider refactoring so getInput()becomes a one-line call to a thorough getInput(Scanner)method you write, which you could easily test by creating your own Scanner("your\ntest\ninput\n"). There are a number of other ways to inject your scanner dependency, like making a field you overwrite for testing, but just making a method overload is extremely easy and technically gives you more flexibility (letting you add a feature to read input from a File, for instance).

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

In general, remember to design for ease of testing, and test the high-risk parts more heavily than the low-risk parts. This means that refactoring is a good tool, and that testing getInput(Scanner)is likely much more important than testing getInput(), especially as you do more than just calling nextLine().

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

I would recommend heavily against creating a mock Scanner: Not only is it bad practice to mock a type you don't own, but Scanner represents a very large API of interrelated methods where call order matters. To replicate it in Mockito means that either you would create a big fake Scanner implementation in Mockito or mock a minimal implementation that tests only the calls you make (and breaks if your implementation changes, even if your changes provide a correct result). Use a real Scanner and save Mockito practice for external service calls or cases where you're mocking a small yet-unwritten API you define.

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

回答by Prahalad Deshpande

First of all I assume that the objective of your test is to verify that user input is obtained from the scanner and that the value returned is what has been input in the scanner.

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

The reason why you mocking does not work is because you are creating the actualscanner object every time within the getInput()method. Hence no matter what you do your mockito instance is never called. Hence the correct way to make this class testable would be to identify all the external dependencies for the class (in this case the java.util.Scannerand inject them into the class through the constructor. This way you can inject the mock Scanner instance during testing. This is a basic step towards dependency injection which in turn leads to good TDD. An example would help you:

您嘲笑不起作用的原因是因为您每次都在方法中创建实际的扫描仪对象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();
        }
    }

Now your test method:

现在你的测试方法:

@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();
}

Also note that since in the above class I am injecting using a constructor, I have declare the Scanner instance final. Since I have no more mutable state in this class this class is thread-safe.

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

The concept of constructor-based dependency injection is pretty cool and worth reading up on the internet. It helps a big way to develop good thread-safe testable code.

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

回答by Stefan Birkner

You can write a clear test for the command line interface by using the TextFromStandardInputStreamrule of the System Ruleslibrary.

您可以使用系统规则库的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());
  }
}