1. 概述
在测试依赖于控制台用户输入的代码时,这个过程可能会变得相当具有挑战性。尤其对于基于控制台或独立的应用程序,测试用户输入场景是至关重要的,我们需要确保对各种输入的正确处理。
本教程将探讨如何使用JUnit来测试System.in
。
2. 理解System
类
在深入之前,让我们先看看System
类。它来自java.lang
包中的一个最终类。
该类通过in
和out
变量提供对标准输入和输出流的访问。与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.in
和System.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上找到。