• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

奋斗的软件工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

关于JUnit测试环境中标准输入流的注意事项

关于JUnit测试环境中标准输入流的注意事项

小提示:避免在JUnit测试中使用控制台输入

当你在开发Java应用程序时,可能会遇到需要从用户获取输入的情况。然而,在编写JUnit测试时,我们应该注意到JUnit测试环境的特性,以确保测试的有效性和可靠性。

问题描述:

在尝试通过System.in(例如使用Scanner)读取用户输入时,如果在JUnit测试方法中执行,你可能会发现程序并没有如预期那样暂停并等待输入。相反,控制台可能不会打印出提示信息,或者即使打印了,也无法接收用户输入。这是因为JUnit测试运行器默认情况下并不提供标准输入流(stdin),JUnit测试是非交互式的,旨在自动化和快速执行,而不是与用户互动。

解决方案:

  1. 模拟输入数据:
    在单元测试中,推荐的做法是不依赖于真实的用户输入。可以使用预先设定的数据来模拟用户的输入,这可以通过参数化测试或使用mock对象来实现。例如,可以将用户名和密码作为参数传递给测试方法,而不是从控制台读取它们。

  2. 重构代码结构:
    如果你的方法确实需要处理用户输入,考虑将其逻辑分离到一个独立的方法中,并在该方法中进行输入处理。然后,在测试中调用这个方法,传入模拟的输入值。

  3. 使用系统属性或环境变量:
    对于一些配置项,比如数据库连接信息,可以通过设置系统属性或环境变量来替代直接从控制台读取。

  4. 外部文件或资源:
    对于更复杂的数据输入场景,可以考虑从外部文件加载测试数据,这样不仅可以简化测试过程,还可以更好地管理测试用例。

  5. 更新测试方式:
    如果你确实需要模拟控制台输入来进行测试,可以使用像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来加载测试数据,为不同的测试场景提供了灵活性。

这种方法不仅使测试更加可靠,而且提高了代码的可维护性和可测试性。记住,好的单元测试应该是快速、可靠且易于理解的,而不应该依赖于外部因素,如用户输入。

posted on 2024-12-08 11:56  周政然  阅读(24)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3