如何用JUnit4测试Java输入输出?测试挂起问题求解
问题与解决方案
问题描述
编写UserInteraction类的JUnit4测试用例时,模拟输入"1\nJohn\n"测试添加用户功能,测试执行出现挂起:仅第一个输入"1"被读取,后续输入无法识别,改用System.lineSeparator()替换\n也无法解决。
问题根源分析
1. Scanner重复创建导致输入流被关闭
readInput()方法每次新建Scanner(System.in),调用nextLine()后,Scanner会自动关闭底层的System.in流(即使未显式调用close,Scanner的finalize方法也会处理)。第一次读取后System.in被关闭,后续调用readInput时会因流已关闭而阻塞,导致测试挂起。
2. 测试类调用入口错误
测试代码中调用program.main(null),但Program类并无main方法,正确入口应为UserInteraction类的main方法。
3. 循环无法退出
UserInteraction的start方法循环条件!program.getProgramStarted,但Program类未定义该属性/方法,导致循环无限执行,测试无法正常结束。
分步解决方案
修复UserInteraction类
(1)复用Scanner实例
将Scanner作为类成员变量,避免每次读取都新建并关闭输入流:
package program; import java.util.Scanner; public class UserInteraction { private Program program; private Scanner scanner; private PrintStream out; // 生产环境构造方法 private UserInteraction(){ this(new Program(), System.in, System.out); } // 测试用构造方法,支持依赖注入 public UserInteraction(Program program, InputStream in, PrintStream out) { this.program = program; this.scanner = new Scanner(in); this.out = out; } private void start() { boolean running = true; while (running) { String input = readInput(); switch (input) { case "1": addUser(); break; case "2": deleteUser(); break; case "4": // 新增退出选项,终止循环 running = false; break; default: out.println("Unknown option"); break; } } scanner.close(); } private void addUser() { out.println("Enter username:"); String username = readInput(); try { program.addUser(username); out.println("User " + username + " added"); // 修正原代码的空格缺失 } catch (Exception e) { out.println("Failed to add user: " + e.getMessage()); } } private void deleteUser() { out.println("Enter username to delete:"); String username = readInput(); program.deleteUser(username); out.println("User " + username + " deleted"); } private String readInput() { return scanner.nextLine(); // 复用同一个Scanner } public static void main(String[] args) { new UserInteraction().start(); } }
(2)完善Program类
补充缺失的deleteUser方法、User内部类,修正命名规范:
package program; import java.util.ArrayList; import java.util.List; public class Program { private User currentUser; private List<User> users = new ArrayList<>(); public void addUser(String name) throws Exception { if (users.size() >= 6) { throw new Exception("Maximum user limit reached (6 users)"); } int newID = users.size() + 1; users.add(new User(name, newID)); } public void deleteUser(String name) { users.removeIf(user -> user.getName().equals(name)); } public String[] getAllUsers() { String[] userArray = new String[users.size()]; int counter = 0; for (User user : users) { userArray[counter++] = user.toString(); } return userArray; } public static class User { private String name; private int id; public User(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } @Override public String toString() { return "User{id=" + id + ", name='" + name + "'}"; } } }
修复JUnit测试类
调用正确的入口方法,模拟退出输入让循环正常结束:
package tests; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.After; import org.junit.Test; import static org.junit.Assert.assertTrue; import program.UserInteraction; public class UserInteractionTest { private final PrintStream originalOut = System.out; @After public void restoreStreams() { System.setOut(originalOut); } @Test public void testCreateUser() throws Exception { // 模拟输入:选择添加用户 -> 输入John -> 退出程序 String input = "1\nJohn\n4\n"; System.setIn(new ByteArrayInputStream(input.getBytes())); ByteArrayOutputStream outContent = new ByteArrayOutputStream(); System.setOut(new PrintStream(outContent)); UserInteraction.main(null); assertTrue("Output should contain user added message", outContent.toString().contains("User John added")); } }
测试Java输入输出的更优方案
1. 依赖注入输入输出流
将输入输出流作为类构造参数,完全解耦对System.in/System.out的依赖,测试时直接传入模拟流,无需修改全局变量:
// 测试类示例 @Test public void testCreateUserWithDI() throws Exception { String input = "1\nJohn\n4\n"; ByteArrayInputStream in = new ByteArrayInputStream(input.getBytes()); ByteArrayOutputStream outContent = new ByteArrayOutputStream(); PrintStream out = new PrintStream(outContent); Program program = new Program(); UserInteraction ui = new UserInteraction(program, in, out); ui.start(); // 需将start方法改为public,或提供测试专用入口 assertTrue(outContent.toString().contains("User John added")); }
2. 使用专用测试框架
- System Rules:专门用于测试
System.in/System.out的JUnit规则,简化流的替换与恢复:
import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.SystemOutRule; import org.junit.contrib.java.lang.system.TextFromStandardInputStream; import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.emptyStandardInputStream; import static org.junit.Assert.assertTrue; import program.UserInteraction; public class UserInteractionTestWithSystemRules { @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Rule public final SystemOutRule systemOutRule = new SystemOutRule().enableLog(); @Test public void testCreateUser() { systemInMock.provideLines("1", "John", "4"); UserInteraction.main(null); assertTrue(systemOutRule.getLog().contains("User John added")); } }
内容的提问来源于stack exchange,提问作者mlhb
相关产品推荐
相关产品推荐

