在junit测试中模拟用户控制台输入

时间:2018-04-29 03:21:00

标签: java junit mocking

我试图通过控制台模拟多个用户输入,我在阅读'> 1'时遇到问题作为输入。只阅读' 1'似乎很好。

考虑以下方法进行测试:

public int getMainMenuSelectedOption() {
    Scanner scan = new Scanner(System.in);
    int i = scan.nextInt();
    while ((i < 1) || (i > 5)) {
      System.out.println("Invalid option, please select again");
      i = scan.nextInt();
    }
    return i;
}

我这样写了测试

private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();

@Before
public void setUpStreams() {
    System.setOut(new PrintStream(outContent));
    System.setErr(new PrintStream(errContent));
}

@Test
public void testGetMainMenuSelectedOptionNotifyInvalidOption() {
    ByteArrayInputStream in = new ByteArrayInputStream("0".getBytes());
    System.setIn(in);
    app.getMainMenuSelectedOption();
    assertEquals("Invalid option, please select again", outContent.toString());
    ByteArrayInputStream in2 = new ByteArrayInputStream("1".getBytes());
    System.setIn(in2);
}

// Also tried with SequenceInputStream. This will not get the expected outstream of "Invalid option".
@Test
public void testGetMainMenuSelectedOptionNotifyInvalidOption() {
    ByteArrayInputStream in1 = new ByteArrayInputStream("0".getBytes());
    ByteArrayInputStream in2 = new ByteArrayInputStream("1".getBytes());
    SequenceInputStream in = new SequenceInputStream(in1,in2);
    System.setIn(in);
    app.getMainMenuSelectedOption();
    assertEquals("Invalid option, please select again\n", outContent.toString());
}

// This test runs fine
@Test
public void testGetMainMenuSelectedValidOption() {
    ByteArrayInputStream in = new ByteArrayInputStream("1".getBytes());
    System.setIn(in);
    app.getMainMenuSelectedOption();   
}

我收到以下错误

java.util.NoSuchElementException
    at java.util.Scanner.throwFor(Scanner.java:862)
    at java.util.Scanner.next(Scanner.java:1485)
    at java.util.Scanner.nextInt(Scanner.java:2117)
    at java.util.Scanner.nextInt(Scanner.java:2076)
    at com.twu.biblioteca.BibliotecaApp.getMainMenuSelectedOption(BibliotecaApp.java:42)
    at com.twu.biblioteca.ExampleTest.testGetMainMenuSelectedOptionNotifyInvalidOption(ExampleTest.java:73)

3 个答案:

答案 0 :(得分:3)

在你的方法中:

   Scanner scan = new Scanner(System.in);
   int i = scan.nextInt();
   while ((i < 1) || (i > 5)) {
        System.out.println("Invalid option, please select again");
        i = scan.nextInt();
    }
    return i;
输入0

将元素0读取到i,然后进入循环(因为条件为i<1),然后打印一条消息,然后尝试扫描循环中的下一个元素。因为输入流已经耗尽 根据{{​​3}}:

  

<强>抛出:
  NoSuchElementException - 如果输入用尽

在我看来,一切都很好(除了方法逻辑中的错误)。

<强> ---------- 编辑 ----------

  

有效输入应为1到4,包括两者。为什么输入   流累了?

我不知道为什么你不使用调试器来检查你的代码中发生了什么,只需点击IDE右边栏中的某行来设置断点,然后右键单击你的测试方法并选择& #34;调试&#34;从上下文菜单中。

无论如何,让我们在纸上逐步调试输入0的方法:

1   Scanner scan = new Scanner(System.in);
2   int i = scan.nextInt();
3   while ((i < 1) || (i > 5)) {
4        System.out.println("Invalid option, please select again");
5        i = scan.nextInt();
6    }
7    return i;

输入缓冲区包含单个字符0

  • 执行流程移至第2行
  • 在第2行,扫描程序从输入流中读取字符0到变量i。变量i现在包含0值。在此之后输入流为空(已耗尽)
  • 在第3行中检查条件((i < 1) || (i > 5))。由于i等于0,条件为真,程序流进入循环
  • 第4行中的
  • 消息将打印到控制台
  • 在第5行,扫描仪试图从输入缓冲区读取下一个整数。 由于输入缓冲区已耗尽,因此抛出异常

我认为现在应该很清楚。为什么输入流耗尽?因为它只有一个元素0在第2行被引用,所以在第5行它是空的(耗尽)
 您的代码有错误 - 但这就是我们进行单元测试以查找此类错误的原因)。
提示:使用the documentation of Scanner#nextInt()方法测试输入流中是否存在下一个int以避免异常。

答案 1 :(得分:1)

我建议你采用一种处理测试的新方法。

实施您自己的ByteArrayInputStream - &gt;只需将现有的ByteArrayInputStream扩展到您自己的MyTestByteArrayInputStream中。

考虑以下示例:

import java.io.ByteArrayInputStream;
import java.util.stream.*;

public class MyClass {

    public static class MyTestByteArrayInputStream extends ByteArrayInputStream {
        private Byte mockedBuffer = null;

        public MyTestByteArrayInputStream(byte b) {
            super(new byte[] {b});
            this.mockedBuffer = b;
        }

        public MyTestByteArrayInputStream(byte[] in) {
            super(in);
        }
        public MyTestByteArrayInputStream(byte[] in, int offset, int length) {
            super(in,offset,length);
        }

        @Override
        public int read() {
            if(mockedBuffer == null) {
                return super.read();
            }
            else {
                return mockedBuffer;
            }
        }
    }

    public static void main(String args[]) {
        byte b = 49;
        MyTestByteArrayInputStream myTestInputStream = new MyTestByteArrayInputStream(b);
        IntStream.range(0,10).forEach(i -> {
                System.out.println("Invoking #" + i +": " + myTestInputStream.read()); 
            });
    }
}

输出:

Invoking #0: 49
Invoking #1: 49
Invoking #2: 49
Invoking #3: 49
Invoking #4: 49
Invoking #5: 49
Invoking #6: 49
Invoking #7: 49
Invoking #8: 49
Invoking #9: 49

你的模拟实现有很多功能。

在我的示例中,我添加了一个构造函数,它允许您使用单个字节实例化IS,稍后这将是read()重写方法的唯一输出。

这意味着您无需提供&#39;带输入的输入流 - 调用read()方法永远不会导致NoSuchElementException。

我还高度重申你使用一个实际的模拟框架:JMockMockito是非常了解和有用的工具,广泛用于单元测试。

使用模拟框架可以节省实际实现MyTestByteArrayInputStream的需要 - 而只是简单地覆盖特定ByteArrayInputStream实例的行为。

希望这有助于并随意询问/评论此解决方案。

答案 2 :(得分:0)

我已经想过,要设置顺序控制台输入流,只需添加&#34; \ n&#34;作为换行。

例如,我想要一个&#39; 0&#39;其次是&#39; 1&#39;控制台输入,所以以下工作

    ByteArrayInputStream in = new ByteArrayInputStream("0\n1\n".getBytes());
    System.setIn(in);
    app.getMainMenuSelectedOption();
    assertEquals("Invalid option, please select again\n", outContent.toString());