关于JUnit测试环境中标准输入流的注意事项
关于JUnit测试环境中标准输入流的注意事项
小提示:避免在JUnit测试中使用控制台输入
当你在开发Java应用程序时,可能会遇到需要从用户获取输入的情况。然而,在编写JUnit测试时,我们应该注意到JUnit测试环境的特性,以确保测试的有效性和可靠性。
问题描述:
在尝试通过System.in(例如使用Scanner)读取用户输入时,如果在JUnit测试方法中执行,你可能会发现程序并没有如预期那样暂停并等待输入。相反,控制台可能不会打印出提示信息,或者即使打印了,也无法接收用户输入。这是因为JUnit测试运行器默认情况下并不提供标准输入流(stdin),JUnit测试是非交互式的,旨在自动化和快速执行,而不是与用户互动。
解决方案:
- 
模拟输入数据: 
 在单元测试中,推荐的做法是不依赖于真实的用户输入。可以使用预先设定的数据来模拟用户的输入,这可以通过参数化测试或使用mock对象来实现。例如,可以将用户名和密码作为参数传递给测试方法,而不是从控制台读取它们。
- 
重构代码结构: 
 如果你的方法确实需要处理用户输入,考虑将其逻辑分离到一个独立的方法中,并在该方法中进行输入处理。然后,在测试中调用这个方法,传入模拟的输入值。
- 
使用系统属性或环境变量: 
 对于一些配置项,比如数据库连接信息,可以通过设置系统属性或环境变量来替代直接从控制台读取。
- 
外部文件或资源: 
 对于更复杂的数据输入场景,可以考虑从外部文件加载测试数据,这样不仅可以简化测试过程,还可以更好地管理测试用例。
- 
更新测试方式: 
 如果你确实需要模拟控制台输入来进行测试,可以使用像System.setIn()这样的方法临时替换System.in,并提供一个包含所需输入的InputStream。
前后代码对比
原始代码(存在控制台输入)
package com.itcq.d_test;
import org.junit.Test;
import java.sql.*;
import java.util.Objects;
import java.util.Scanner;
public class App {
    public static String url;
    public static String user;
    public static String password;
    static{
        url="jdbc:mysql://127.0.0.1:3309/db_jdbc?useSSL=false";
        user="root";
        password="928151";
    }
    @Test
    public void testStatement(){
        Scanner scanner = null;
        Connection connection=null;
        Statement statement=null;
        ResultSet resultSet=null;
        try {
            Class clazz = Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection(url, user, password);
            statement = connection.createStatement();
            // 控制台输入部分
            scanner = new Scanner(System.in);
            System.out.print("Please Enter userName:");
            String userName=scanner.nextLine();
            System.out.println();
            System.out.print("Please Enter passWord:");
            String passWord=scanner.nextLine();
            String sql="select *from tb_user where username='"+userName+"' and password = '"+passWord+"'";
            resultSet = statement.executeQuery(sql);
            if(resultSet.next())
                System.out.println("Login Success~~~");
            else
                System.out.println("Login Failure!!!");
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if(!Objects.isNull(resultSet)) resultSet.close();
                if(!Objects.isNull(statement)) statement.close();
                if(!Objects.isNull(connection)) connection.close();
                if(!Objects.isNull(scanner)) scanner.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
改进后的代码(移除控制台输入,使用预定义数据)
package com.itcq.d_test;
import org.junit.Test;
import java.sql.*;
import java.util.ResourceBundle;
import static org.junit.Assert.*;
public class App {
    private static final String URL = "jdbc:mysql://127.0.0.1:3309/db_jdbc?useSSL=false";
    private static final String USER = "root";
    private static final String PASSWORD = "928151";
    /**
     * 模拟登录验证的方法。
     */
    public boolean validateUser(String userName, String passWord) {
        try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
             PreparedStatement statement = connection.prepareStatement(
                     "SELECT COUNT(*) FROM tb_user WHERE username = ? AND password = ?")) {
            statement.setString(1, userName);
            statement.setString(2, passWord);
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    return resultSet.getInt(1) > 0;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }
    @Test
    public void testValidateUser() {
        App app = new App();
        // 使用预定义的数据进行测试
        assertTrue("Expected login success with correct credentials", 
                   app.validateUser("expectedUsername", "expectedPassword"));
        assertFalse("Expected login failure with incorrect credentials", 
                    app.validateUser("wrongUsername", "wrongPassword"));
        // 或者使用ResourceBundle来加载测试数据
        ResourceBundle user = ResourceBundle.getBundle("user");
        String userName = user.getString("userName");
        String passWord = user.getString("passWord");
        assertTrue("Expected login success using ResourceBundle",
                   app.validateUser(userName, passWord));
    }
}
改进说明:
- 移除了静态初始化块:现在使用private static final字段来保存数据库连接信息。
- 引入了validateUser方法:这个方法接收用户名和密码作为参数,并执行查询以验证凭据。不再依赖控制台输入。
- 使用PreparedStatement:提高了安全性,防止SQL注入风险。
- 资源管理:使用了try-with-resources语句来自动关闭Connection、PreparedStatement和ResultSet,简化了资源释放的代码。
- 单元测试:在testValidateUser中,我们不再依赖控制台输入,而是直接提供测试数据来验证validateUser方法的行为。此外,还展示了如何使用ResourceBundle来加载测试数据,为不同的测试场景提供了灵活性。
这种方法不仅使测试更加可靠,而且提高了代码的可维护性和可测试性。记住,好的单元测试应该是快速、可靠且易于理解的,而不应该依赖于外部因素,如用户输入。
 
                    
                     
                    
                 
                    
                 
         
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号