为了测试我们所有应用程序的运行状况,我们在每个应用程序中都包含一个Healthcheck servlet。这些运行状况检查仅测试每个应用程序的依赖性。这些依赖关系类型之一是Sql服务器连接。为了测试连接,我们有一个称为HealthCheckRunner.Result run()
的方法。 (如下代码所示)。该方法将使用url,用户名和密码,并尝试连接到服务器。
此方法运行良好,但是我发现在我们所有的应用程序中,我仍在重复很多代码以从context.xml中检索url,用户名,密码和驱动程序。为了节省时间和重复,我想使用其他构造函数或工厂方法进行重构,如下面的选项1和2所示。
这两种方法对我似乎都没有吸引力。首先,构造函数很杂乱,而且似乎不太用户友好。其次,静态方法可能难以测试。最后,它们都以ServletContext作为参数。
单元测试静态方法会很难吗?为简单起见,我宁愿远离PowerMock并仅使用Mockito。而且,是否会为我创建的每个SqlHealthCheck创建ServletContext的副本?还是他们都会使用相同的参考?而且,由于我仅使用上下文中的一些值,因此最好创建另一个类并仅传递我需要的值?我想出的解决方案不是很好,我知道必须有更好的方法。
public class SqlHealthCheck extends HealthCheck {
private String url;
private String username;
private String password;
private String driver;
// Option 1: Constructor with ServletContext as parameter.
public SqlHealthCheck (ServletContext context, String prefix) {
this.url = context.getInitParameter(prefix + ".db-url");
this.username = context.getInitParameter(prefix + ".db-user");
this.password = context.getInitParameter(prefix + ".db-passwd");
setType("sqlcheck");
setDescription("SQL database check: " + this.url);
this.decodePassword();
this.setDriver(context.getInitParameter(prefix + ".db-driver"));
}
// Option 2: Static factory method with ServletContext as parameter
public static HealthCheck createHealthCheck(ServletContext context, String prefix) {
String dbUrl = context.getInitParameter(prefix + ".db-url");
String username = context.getInitParameter(prefix + ".db-user");
String password = context.getInitParameter(prefix + ".db-passwd");
String sqlDriver = context.getInitParameter(prefix + ".db-driver");
SqlHealthCheck healthCheck = new SqlHealthCheck("SQL database check: " + dbUrl, dbUrl, username, password);
healthCheck.decodePassword();
healthCheck.setDriver(sqlDriver);
return healthCheck;
}
public HealthCheckRunner.Result run() {
Connection connection = null;
Statement statement = null;
try {
if (driver != null) { Class.forName(driver); }
connection = DriverManager.getConnection(this.url, this.username, this.password);
statement = connection.createStatement();
statement.executeQuery("SELECT 1");
return HealthCheckRunner.Result.Pass;
} catch (SQLException | ClassNotFoundException ex) {
setMessage(ex.getMessage());
return getFailureResult();
}
finally {
try {
if (statement != null) {statement.close();}
if (connection != null) {connection.close();}
} catch (SQLException ex) {
setMessage(ex.getMessage());
}
}
}
public void decodePassword() {
// Decode password
try {
if (password != null && !"".equals(password)) {
password = new String(Base64.decode(password.getBytes()));
}
} catch (Exception e) {
if (e.getMessage()!=null) {
this.setMessage(e.getMessage());}
}
}
}
答案 0 :(得分:0)
我发现在我们所有的应用程序中,我仍在重复很多代码以从context.xml中检索url,用户名,密码和驱动程序
4行代码远不止很多代码。但是,如果您实际上是在所有应用程序中复制和粘贴此类,则根本不应该这样做。创建一个包含可重复使用的运行状况检查的单独项目,生成一个jar,然后在需要运行状况检查的每个应用程序中使用此jar。
构造函数非常混乱,而且似乎不太用户友好
坦白说,这并不那么混乱。但是,如果您不重复自己,以相同的方式初始化私有字段,以及将可比较的代码分组在一起,则可能不会太杂乱:
public SqlHealthCheck (ServletContext context, String prefix) {
this.url = getParam(context, prefix, "db-url");
this.username = getParam(context, prefix, "db-user");
this.password = getParam(context, prefix, "db-password");
this.driver = getParam(context, prefix, "db-driver");
this.decodePassword();
setType("sqlcheck");
setDescription("SQL database check: " + this.url);
}
单元测试静态方法会很难吗?
不。 ServletContext是一个接口。因此,您可以创建自己的假实现或使用模拟框架对其进行模拟。然后,您只需调用factory方法的构造函数,运行状况检查,然后查看它是否返回正确的值即可。
是否为我创建的每个SqlHealthCheck创建ServletContext的副本?
当然不是。 Java按值将引用传递给对象。
创建另一个类并仅传递我需要的值会更好吗?
您可以这样做,但是从servlet上下文获取值的逻辑将在其他地方,并且您也必须对其进行测试,基本上以与测试此类相同的方式进行测试。