关于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号