如何使用JUnit测试执行中途需用户键盘输入的Java方法
问题根因
测试阻塞的核心原因有两个:
- 代码执行顺序错误:
System.setIn()重定向标准输入的逻辑必须在调用读取输入的被测方法之前执行。现有代码先调用了myclass.readInt(),方法启动后直接读取原生System.in等待键盘输入,后续的重定向逻辑根本没有执行机会,自然会卡住。 - 被测代码存在隐藏缺陷:方法内调用
scan.close()时,会连带关闭底层绑定的全局System.in流,即使当前用例跑通,同测试套件内其他依赖标准输入的用例也会抛出流已关闭的异常。
修复实现
核心步骤
- 提前构造模拟输入流,完成
System.setIn()重定向后,再调用被测方法 - 测试执行前备份原生标准输入流,测试结束后恢复,避免污染全局测试环境
- 移除被测方法中对绑定
System.in的Scanner的close调用,防止全局输入流被意外关闭
可直接运行的代码示例
import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Scanner; import static org.junit.Assert.assertEquals; // 修正后的被测类 public class MyClass{ public int readInt() { Scanner scan = new Scanner(System.in); System.out.print("Enter number: "); int num = scan.nextInt(); // 移除scan.close(),避免关闭全局System.in return num; } } public class MyClassTest { // 备份原生标准输入流,用于测试后恢复 private final InputStream originalSystemIn = System.in; @Test public void testReadInt() { // 第一步:先构造模拟输入,完成重定向 String mockInput = "4"; InputStream mockInputStream = new ByteArrayInputStream(mockInput.getBytes()); System.setIn(mockInputStream); // 第二步:重定向完成后调用被测方法,不会阻塞 MyClass myclass = new MyClass(); int calcResult = myclass.readInt(); // 第三步:结果断言 assertEquals(4, calcResult); // 第四步:恢复原生标准输入 System.setIn(originalSystemIn); } }
无法修改被测代码时的兼容方案
如果业务代码中已经写死Scanner关闭逻辑,无法直接修改,可以通过匿名内部类重写模拟流的close方法,屏蔽关闭操作,避免全局流被销毁:
InputStream mockInputStream = new ByteArrayInputStream("4".getBytes()) { @Override public void close() { // 空实现,不执行实际流关闭操作 } }; System.setIn(mockInputStream);
如果测试类中有多个依赖模拟输入的用例,可以把流备份、恢复的逻辑抽到@Before/@After(JUnit4)或@BeforeEach/@AfterEach(JUnit5)生命周期方法中,减少重复代码。
内容的提问来源于stack exchange,提问作者devOn
相关产品推荐
相关产品推荐

