You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:10:09