使用ServletContext作为参数的工厂方法与构造方法

时间:2018-09-17 05:41:41

标签: java unit-testing servlets

为了测试我们所有应用程序的运行状况,我们在每个应用程序中都包含一个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());}
    }
}

}

1 个答案:

答案 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上下文获取值的逻辑将在其他地方,并且您也必须对其进行测试,基本上以与测试此类相同的方式进行测试。