Java 使用模拟用户输入进行JUnit测试

Java 使用模拟用户输入进行JUnit测试,java,eclipse,junit,user-input,Java,Eclipse,Junit,User Input,我正在尝试为需要用户输入的方法创建一些JUnit测试。测试中的方法看起来有点像以下方法: public static int testUserInput() { Scanner keyboard = new Scanner(System.in); System.out.println("Give a number between 1 and 10"); int input = keyboard.nextInt(); while (input < 1 ||

我正在尝试为需要用户输入的方法创建一些JUnit测试。测试中的方法看起来有点像以下方法:

public static int testUserInput() {
    Scanner keyboard = new Scanner(System.in);
    System.out.println("Give a number between 1 and 10");
    int input = keyboard.nextInt();

    while (input < 1 || input > 10) {
        System.out.println("Wrong number, try again.");
        input = keyboard.nextInt();
    }

    return input;
}
publicstaticinttestuserinput(){
扫描仪键盘=新扫描仪(System.in);
System.out.println(“给出一个介于1和10之间的数字”);
int input=keyboard.nextInt();
while(输入<1 | |输入>10){
System.out.println(“错误的号码,再试一次。”);
输入=键盘.nextInt();
}
返回输入;
}
在JUnit测试方法中,是否有一种可能的方法可以自动向程序传递一个int,而不是我或其他人手动传递?比如模拟用户输入


提前感谢。

您可以从提取从键盘检索数字的逻辑到它自己的方法开始。然后,您可以测试验证逻辑,而不用担心键盘。为了测试键盘.nExtTin()调用,您可能需要考虑使用模拟对象。

可以替换。 InputStream可以是字节数组:

InputStream sysInBackup = System.in; // backup System.in to restore it later
ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);

// do your thing

// optionally, reset System.in to its original
System.setIn(sysInBackup);
通过传入和传出参数,可以使用不同的方法使此方法更易于测试:

public static int testUserInput(InputStream in,PrintStream out) {
    Scanner keyboard = new Scanner(in);
    out.println("Give a number between 1 and 10");
    int input = keyboard.nextInt();

    while (input < 1 || input > 10) {
        out.println("Wrong number, try again.");
        input = keyboard.nextInt();
    }

    return input;
}
publicstaticinttestuserinput(InputStream-in,PrintStream-out){
扫描仪键盘=新扫描仪(英寸);
out.println(“给出一个介于1和10之间的数字”);
int input=keyboard.nextInt();
while(输入<1 | |输入>10){
out.println(“错误的号码,再试一次。”);
输入=键盘.nextInt();
}
返回输入;
}

我发现创建一个定义类似于java.io.Console的方法的接口很有帮助,然后使用该接口读取或写入System.out。真正的实现将委托给System.console(),而JUnit版本可以是具有固定输入和预期响应的模拟对象


例如,您可以构造一个MockConsole,其中包含来自用户的固定输入。每次调用readLine时,模拟实现都会从列表中弹出一个输入字符串。它还将收集写入响应列表的所有输出。在测试结束时,如果一切顺利,那么您的所有输入都将被读取,您可以在输出上断言。

要测试您的代码,您应该为系统输入/输出函数创建一个包装器。您可以使用依赖项注入实现这一点,为我们提供了一个可以请求新整数的类:

public static class IntegerAsker {
    private final Scanner scanner;
    private final PrintStream out;

    public IntegerAsker(InputStream in, PrintStream out) {
        scanner = new Scanner(in);
        this.out = out;
    }

    public int ask(String message) {
        out.println(message);
        return scanner.nextInt();
    }
}
然后,您可以使用模拟框架(我使用Mockito)为您的函数创建测试:

然后编写通过测试的函数。该函数更简洁,因为您可以删除请求/获取整数复制,并且实际的系统调用被封装

public static void main(String[] args) {
    getBoundIntegerFromUser(new IntegerAsker(System.in, System.out));
}

public static int getBoundIntegerFromUser(IntegerAsker asker) {
    int input = asker.ask("Give a number between 1 and 10");
    while (input < 1 || input > 10)
        input = asker.ask("Wrong number, try again.");
    return input;
}
publicstaticvoidmain(字符串[]args){
getBoundIntegerFromUser(新的IntegerAsker(System.in,System.out));
}
公共静态int getBoundIntegerFromUser(IntegerAsker){
int input=asker.ask(“给出一个介于1和10之间的数字”);
while(输入<1 | |输入>10)
input=asker.ask(“打错号码,再试一次”);
返回输入;
}

对于您的小示例来说,这似乎有些过火,但如果您正在构建一个更大的应用程序,那么这样的开发可能会很快获得回报。

测试类似代码的一种常见方法是提取一个包含扫描仪和PrintWriter的方法,类似于,并测试:

public void processUserInput() {
  processUserInput(new Scanner(System.in), System.out);
}

/** For testing. Package-private if possible. */
public void processUserInput(Scanner scanner, PrintWriter output) {
  output.println("Give a number between 1 and 10");
  int input = scanner.nextInt();

  while (input < 1 || input > 10) {
    output.println("Wrong number, try again.");
    input = scanner.nextInt();
  }

  return input;
}
当然,您也可以将“scanner”和“output”作为系统中的可变字段进行测试,而不是创建重载方法。我倾向于尽可能地让课堂保持无状态,但如果这对你或你的同事/老师很重要,那也不是一个很大的让步


您还可以选择将测试代码放在与被测试代码相同的Java包中(即使它位于不同的源文件夹中),这允许您将两个参数重载的可见性放宽为包私有。

我设法找到了一种更简单的方法。但是,您必须使用@Stefan Birkner提供的外部库

我只是举了一个例子,我认为它再简单不过了:

import java.util.Scanner;

public class Summarize {
  public static int sumOfNumbersFromSystemIn() {
    Scanner scanner = new Scanner(System.in);
    int firstSummand = scanner.nextInt();
    int secondSummand = scanner.nextInt();
    return firstSummand + secondSummand;
  }
}
试验


问题是,在我的例子中,我的第二个输入有空格,这使得整个输入流为空

我已经修复了从stdin读取以模拟控制台的问题

我的问题是我想尝试在JUnit测试控制台中编写一个特定的对象

问题就像您所说的:我如何从JUnit测试中写入Stdin

然后在大学里我学习了重定向,就像你说的System.setIn(InputStream)更改stdin文件描述符,然后你就可以写了

但还有一个问题需要解决。。。JUnit测试块正在等待从新的InputStream读取,因此您需要创建一个线程来从InputStream读取,并从JUnit测试线程写入新的Stdin。。。首先,您必须在Stdin中写入,因为如果您稍后写入或创建从Stdin读取的线程,您可能会遇到争用条件。。。您可以在读取之前写入InputStream,也可以在写入之前从InputStream读取

这是我的代码,我的英语水平很差,我希望大家都能理解这个问题以及JUnit测试中模拟编写标准文本的解决方案

private void readFromConsole(String data) throws InterruptedException {
    System.setIn(new ByteArrayInputStream(data.getBytes()));

    Thread rC = new Thread() {
        @Override
        public void run() {
            study = new Study();
            study.read(System.in);
        }
    };
    rC.start();
    rC.join();      
}

你到底在测试什么?扫描仪?测试方法通常应该断言一些有用的东西,而不必测试Java的Scanner类。您可以手动设置输入,只需测试自己的逻辑即可。int input=-1或5或11将涵盖六年后的逻辑。。。这仍然是一个好问题。不仅仅是因为在开始开发应用程序时,您通常不希望拥有JavaFX的所有功能,而是开始使用简单的命令行进行一些非常基本的交互。可惜JUnit并没有让这件事变得容易一点。对我来说,奥马尔·埃尔沙尔的答案很好,只涉及最小的“人为”或“扭曲”的应用程序编码……注意,你不能模拟扫描仪对象(它们是最终的)。@KrzyH我该怎么做
import java.util.Scanner;

public class Summarize {
  public static int sumOfNumbersFromSystemIn() {
    Scanner scanner = new Scanner(System.in);
    int firstSummand = scanner.nextInt();
    int secondSummand = scanner.nextInt();
    return firstSummand + secondSummand;
  }
}
import static org.junit.Assert.*;
import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.TextFromStandardInputStream;

public class SummarizeTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void summarizesTwoNumbers() {
    systemInMock.provideLines("1", "2");
    assertEquals(3, Summarize.sumOfNumbersFromSystemIn());
  }
}
private void readFromConsole(String data) throws InterruptedException {
    System.setIn(new ByteArrayInputStream(data.getBytes()));

    Thread rC = new Thread() {
        @Override
        public void run() {
            study = new Study();
            study.read(System.in);
        }
    };
    rC.start();
    rC.join();      
}