使用JUnit对System.in进行单元测试

2023/07/29

1. 概述

当测试依赖于控制台用户输入的代码时,该过程可能会变得非常具有挑战性。此外,测试用户输入场景是基于控制台或独立应用程序的关键部分,因为我们需要确保正确处理不同的输入。

在本教程中,我们将介绍使用JUnit测试System.in的方法。

2. 理解System类

在深入研究之前,让我们先看一下System类,它是java.lang包中的最终类。

该类通过in和out变量提供对标准输入和输出流的访问。与out变量类似,System类具有表示标准错误输出流的err变量。

此外,这些变量允许我们读取和写入控制台。使用这些流,我们允许用户从控制台与我们的应用程序交互。

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

3. 输入示例

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

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

Java提供了Scanner类,它允许我们从各种来源读取输入,包括标准键盘输入。此外,它提供了读取用户输入的最简单方法。使用Scanner,我们可以读取任何原始数据类型或String。

在我们的示例方法中,我们使用next()方法来读取用户的输入。此外,该方法将输入中的下一个单词作为字符串读取。

4. 使用核心Java

对标准输入进行单元测试的第一种方法包括Java API提供的功能。

我们可以利用System.in创建自定义输入流并在测试过程中模拟用户输入

但是,在编写单元测试之前,让我们在测试类中编写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("Tuyucheng");
    String input = Application.readName();
    assertEquals(NAME.concat("Tuyucheng"), input);
}

5. 使用System Rules库和JUnit 4

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

首先,让我们向pom.xml添加所需的依赖项

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

该库提供了用于测试依赖于System.in和System.out的代码的JUnit Rule。

此外,它允许我们在测试执行期间重定向输入和输出流,这使得模拟用户输入变得很容易。

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

@Rule
public final TextFromStandardInputStream systemIn = emptyStandardInputStream();

最后,让我们编写单元测试:

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

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

此外,测试执行后会恢复原来的System.in。

6. 使用System Lambda库和JUnit 5

值得一提的是,System Rules默认不支持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("Tuyucheng")
        .execute(() -> assertEquals(NAME.concat("Tuyucheng"), Application.readName()));
}

在这里,我们使用System Lambda类中提供的withTextFromSystemIn()静态方法来设置System.in中可用的输入行。

7. 使用System Stubs库和JUnit 4

此外,我们可以使用JUnit 4和System Stubs库测试标准输入。

让我们添加所需的依赖项

<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("Tuyucheng");

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

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

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("Tuyucheng")
        .execute(() -> {
             assertThat(Application.readName()).isEqualTo(NAME.concat("Tuyucheng"));
        });
}

9. 总结

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

通过第一种方法,我们学习了如何使用核心Java功能自定义System.in。在第二种方法中,我们了解了如何使用System Rules库。接下来,我们学习了如何使用System Lambda库通过JUnit 5编写测试。最后,我们了解了如何使用System Stubs库。

与往常一样,示例代码可以在GitHub上找到。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章