1. 概述

在测试依赖于控制台用户输入的代码时,这个过程可能会变得相当具有挑战性。尤其对于基于控制台或独立的应用程序,测试用户输入场景是至关重要的,我们需要确保对各种输入的正确处理。

本教程将探讨如何使用JUnit来测试System.in

2. 理解System

在深入之前,让我们先看看System类。它来自java.lang包中的一个最终类。

该类通过inout变量提供对标准输入和输出流的访问。与out变量类似,System类还有一个err变量,代表标准错误输出流。

此外,这些变量允许我们从控制台读取和写入数据。利用这些流,我们可以让用户通过控制台与我们的应用交互。

接下来,System.in返回一个InputStream,已经打开以从标准输入读取数据。通过System.in,我们可以将键盘输入流重定向到CPU进入我们的应用程序。

3. 示例输入

让我们从这个教程中使用的简单示例开始:

public static final String NAME = "Name: ";

public static String readName() {
    Scanner scanner = new Scanner(System.in);
    String input = scanner.next();
    return NAME.concat(input);
}

Java提供了Scanner类,它允许我们从多种来源读取输入,包括标准键盘输入。此外,它提供了一种最简单的方式来读取用户输入。使用Scanner,我们可以读取任何基本数据类型或字符串。

在我们的示例方法中,我们使用next()方法从用户那里读取输入。此外,该方法还以字符串形式读取输入中的下一个单词。

4. 使用核心Java

测试标准输入的第一个方法是利用Java API提供的功能。

我们可以利用System.in创建自定义InputStream并在测试期间模拟用户输入。

但在编写单元测试之前,让我们在测试类中添加一个辅助方法provideInput()

void provideInput(String data) {
    ByteArrayInputStream testIn = new ByteArrayInputStream(data.getBytes());
    System.setIn(testIn);
}

在方法内部,我们创建一个新的ByteArrayInputStream,并将我们想要的输入作为字节数组传递给它。

此外,我们使用System.setIn()方法将自定义输入流设置为System.in的输入。

现在,让我们为我们的示例方法编写单元测试。我们可以调用应用类的readName()方法,它现在会读取我们的自定义输入:

@Test
void givenName_whenReadFromInput_thenReturnCorrectResult() {
    provideInput("Baeldung");
    String input = Application.readName();
    assertEquals(NAME.concat("Baeldung"), input);
}

5. 使用System规则库和JUnit 4

现在,让我们看看如何使用System规则库和JUnit 4来测试标准输入。

首先,我们需要在pom.xml中添加所需的依赖项

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-rules</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

该库提供了测试依赖于System.inSystem.out代码的JUnit规则,并允许我们在测试执行期间重定向输入和输出流,从而轻松模拟用户输入。

其次,为了测试System.in,我们需要定义一个新的TextFromStandardInputStream规则。我们将使用emptyStandardInputStream()方法初始化变量,使其具有空输入流:

@Rule
public final TextFromStandardInputStream systemIn = emptyStandardInputStream();

最后,编写单元测试:

@Test
public void givenName_whenReadWithSystemRules_thenReturnCorrectResult() {
    systemIn.provideLines("Baeldung");
    assertEquals(NAME.concat("Baeldung"), Application.readName());
}

此外,我们使用provideLines()方法,它接受可变参数并设置它们作为输入。

测试执行后,原始的System.in会被恢复。

6. 使用System Lambda库和JUnit 5

值得注意的是,System规则库默认不支持JUnit 5。然而,他们提供了一个名为System Lambda的库,可以与JUnit 5一起使用。

我们需要在pom.xml中添加另一个依赖项

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-lambda</artifactId>
    <version>1.2.1</version>
    <scope>test</scope>
</dependency>

现在,让我们在测试中使用System Lambda:

@Test
void givenName_whenReadWithSystemLambda_thenReturnCorrectResult() throws Exception {
    withTextFromSystemIn("Baeldung")
      .execute(() -> assertEquals(NAME.concat("Baeldung"), Application.readName()));
}

这里,我们使用SystemLambda类提供的静态方法withTextFromSystemIn()来设置可以从System.in获取的输入行。

7. 使用System Stubs库和JUnit 4

除了上述方法,我们还可以使用JUnit 4和System Stubs库来测试标准输入。

首先,在pom.xml中添加所需的依赖项

<dependency>
    <groupId>uk.org.webcompere</groupId>
    <artifactId>system-stubs-junit4</artifactId>
    <version>2.0.2</version>
    <scope>test</scope>
</dependency>

接着,创建一个SystemInRule并传递所需的输入值:

@Rule
public SystemInRule systemInRule = new SystemInRule("Baeldung");

现在,我们可以在单元测试中使用创建的规则:

@Test
public void givenName_whenReadWithSystemStubs_thenReturnCorrectResult() {
    assertThat(Application.readName()).isEqualTo(NAME.concat("Baeldung"));
}

8. 使用System Stubs库和JUnit 5

要使用System Stubs和JUnit 5测试System.in,我们需要添加另一个依赖项

<dependency>
    <groupId>uk.org.webcompere</groupId>
    <artifactId>system-stubs-jupiter</artifactId>
    <version>2.0.2</version>
</dependency>

为了提供输入值,我们将使用withTextFromSystemIn()方法:

@Test
void givenName_whenReadWithSystemStubs_thenReturnCorrectResult() throws Exception {
    SystemStubs.withTextFromSystemIn("Baeldung")
      .execute(() -> {
        assertThat(Application.readName())
          .isEqualTo(NAME.concat("Baeldung"));
      });
}

9. 总结

在这篇文章中,我们学习了如何使用JUnit 4和JUnit 5测试System.in

通过第一个方法,我们了解了如何使用核心Java特性定制System.in。第二个方法展示了如何使用System Rules库。接着,我们学习了如何使用System Lambda库与JUnit 5编写测试。最后,我们看到了如何使用System Stubs库进行测试。

如往常一样,本文的完整源代码可在GitHub上找到。