如何使用具有简单日志语句的Spock在Java中测试catch块

如何使用具有简单日志语句的Spock在Java中测试catch块,java,unit-testing,spock,Java,Unit Testing,Spock,我有一个简单的Java方法,我想用Spock进行单元测试 private void executeDataLoad(String sql) { Statement snowflakeStatement=null; try { snowflakeStatement = getSnowflakeStatement(); log.info("Importing data into Snowflake");

我有一个简单的Java方法,我想用Spock进行单元测试

private void executeDataLoad(String sql) {
        Statement snowflakeStatement=null;
        try {

            snowflakeStatement = getSnowflakeStatement();
            log.info("Importing data into Snowflake");
            int rowsUpdated = snowflakeStatement.executeUpdate(sql);
            log.info("Rows updated/inserted:  " + rowsUpdated);
        }
        catch (SQLException sqlEx) {
            log.error("Error importing data into Snowflake", sqlEx);
            throw new RuntimeException(sqlEx);
        }finally{
            try {
                if (snowflakeStatement != null)
                    snowflakeStatement.close();
            } catch (SQLException sqlEx) {
                log.error("Error closing the statement", sqlEx);
            }
        }
    }
最后,我想测试一下卡位。它是一个简单的catch块,只记录一条语句。我看到的所有示例都只测试catch块中包含throw关键字的catch块


如何测试以确保执行catch块

最后在try块中删除空签入。由于此空检查,您无法获得任何异常。只需尝试在不检查语句的情况下关闭语句

private void executeDataLoad(String sql) {
    Statement snowflakeStatement=null;
    try {

        snowflakeStatement = getSnowflakeStatement();
        log.info("Importing data into Snowflake");
        int rowsUpdated = snowflakeStatement.executeUpdate(sql);
        log.info("Rows updated/inserted:  " + rowsUpdated);
    }
    catch (SQLException sqlEx) {
        log.error("Error importing data into Snowflake", sqlEx);
        throw new RuntimeException(sqlEx);
    }finally{
        try {
            snowflakeStatement.close();
        } catch (SQLException sqlEx) {
            log.error("Error closing the statement", sqlEx);
        }
    }
}

简单的答案是:您不直接测试私有方法。

相反,好的测试实践是使用必要的参数和注入对象(通常是模拟对象)测试公共方法,以便覆盖公共和私有方法中的所有执行路径。如果不能通过调用公共方法覆盖私有方法代码,则表明

  • 要么你的类不可测试,要么你应该重构
  • 或者(部分)您的私有方法代码无法访问,因此应该删除
  • 或者两者兼而有之
您的代码还遇到实例化自己的依赖项的问题,在本例中是
语句
对象。如果可以将其作为方法参数而不是将其构造为局部变量的方法注入,则可以轻松地注入mock、stub或spy,并使该mock对象按照您的意愿运行,以便测试方法中的不同情况和执行路径

作为旁注,我假设您的记录器是一个
私有静态final
对象。如果您想使它成为非最终的,您可以用模拟记录器来替换它,甚至可以检查在测试期间是否调用了某些日志方法。但也许这对您来说并不重要,您不应该过度指定和测试太多。在我的示例中,我将使其成为非最终版本,以便向您展示什么是可能的,因为您似乎是测试自动化的初学者

回到测试私有方法:由于大多数模拟框架(也是Spock的)都基于通过动态代理对原始类或接口进行子类化或实现,并且私有方法对其子类不可见,因此您也不能覆盖/存根私有方法的行为。这也是为什么在模拟对象上测试私有方法是个坏主意的另一个(技术)原因

让我们假设我们测试的类如下所示(请注意,我对这两个方法进行了包保护,以便能够模拟/存根它们):

package de.scrum_master.stackoverflow.q58072937;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入java.sql.*;
公共类SQLExecutor{
私有static/*final*/Logger log=LoggerFactory.getLogger(SQLExecutor.class);
/*private*/void executedAtLoad(字符串sql){
语句雪花语句=null;
试一试{
snowflakeStatement=getSnowflakeStatement();
log.info(“将数据导入雪花”);
int rowsUpdated=snowflakeStatement.executeUpdate(sql);
log.info(“行更新/插入:”+rowsUpdated);
}捕获(SQLException sqlEx){
log.error(“将数据导入雪花时出错”,sqlEx);
抛出新的运行时异常(sqlEx);
}最后{
试一试{
if(雪花语句!=null)
snowflake语句。close();
}捕获(SQLException sqlEx){
log.error(“关闭语句时出错”,sqlEx);
}
}
}
/*private*/Statement getSnowflakeStatement(){
返回新语句(){
@重写公共结果集executeQuery(字符串sql)抛出SQLException{return null;}
@重写公共int executeUpdate(字符串sql)抛出SQLException{return 0;}
@重写公共void close()引发SQLException{}
@重写公共int getMaxFieldSize()抛出SQLException{return 0;}
@重写公共void setMaxFieldSize(int max)抛出SQLException{}
@重写公共int getMaxRows()抛出SQLException{return 0;}
@重写公共void setMaxRows(int max)抛出SQLException{}
@重写公共void setEscapeProcessing(布尔启用)抛出SQLException{}
@Override public int getQueryTimeout()抛出SQLException{return 0;}
@重写公共void setQueryTimeout(int秒)抛出SQLException{}
@重写公共void cancel()引发SQLException{}
@重写公共SQLWarning getWarnings()引发SQLException{return null;}
@重写公共void clearWarnings()引发SQLException{}
@重写公共void setCursorName(字符串名称)引发SQLException{}
@重写公共布尔执行(字符串sql)抛出SQLException{return false;}
@重写公共结果集getResultSet()引发SQLException{return null;}
@重写公共int getUpdateCount()抛出SQLException{return 0;}
@重写公共布尔getMoreResults()抛出SQLException{return false;}
@重写公共void setFetchDirection(int direction)抛出SQLException{}
@重写公共int getFetchDirection()抛出SQLException{return 0;}
@重写公共void setFetchSize(int行)抛出SQLException{}
@重写公共int getFetchSize()抛出SQLException{return 0;}
@Override public int getResultSetConcurrency()抛出SQLException{return 0;}
@重写公共int getResultSetType()抛出SQLException{return 0;}
@重写公共void addBatch(字符串sql)引发SQLException{}
@重写公共void clearBatch()引发SQLException{}
@Override public int[]executeBatch()抛出SQLException{return new int[0];}
@重写公共连接getConnection()引发SQLException{return null;}
@重写公共布尔getMoreResults(int current)抛出SQLException{return false;}
@重写公共结果集getGeneratedKeys()抛出SQLExc