gpt4 book ai didi

java - 如何使用具有简单日志语句的 Spock 测试 Java 中的 catch block

转载 作者:行者123 更新时间:2023-12-02 09:29:56 24 4
gpt4 key购买 nike

我有一个简单的 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);
}
}
}

我想测试finally中的catch block 。这是一个简单的 catch block ,仅记录一条语句。我见过的所有示例都只测试 catch block 内有 throw 关键字的 catch block 。

如何测试以确保 catch block 已执行?

最佳答案

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

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

  • 要么你的类不能很好地测试,你应该重构
  • 或者您的私有(private)方法代码(部分)无法访问,因此应删除
  • 或者两者的结合。

您的代码还存在实例化其自身依赖项(在本例中为 Statement 对象)的问题。如果您可以将其作为方法参数注入(inject),而不是将其构造为局部变量的方法,那么您可以轻松地注入(inject)模拟、 stub 或 spy ,并使该模拟对象按照您的意愿运行,以便在您的应用程序中测试不同的情况和执行路径。方法。

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

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

让我们假设我们的测试类看起来像这样(请注意,我对这两种方法进行了包保护,以便能够模拟/ stub 它们):

package de.scrum_master.stackoverflow.q58072937;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;

public class SQLExecutor {
private static /*final*/ Logger log = LoggerFactory.getLogger(SQLExecutor.class);

/*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);
}
}
}

/*private*/ Statement getSnowflakeStatement() {
return new Statement() {
@Override public ResultSet executeQuery(String sql) throws SQLException { return null; }
@Override public int executeUpdate(String sql) throws SQLException { return 0; }
@Override public void close() throws SQLException {}
@Override public int getMaxFieldSize() throws SQLException { return 0; }
@Override public void setMaxFieldSize(int max) throws SQLException {}
@Override public int getMaxRows() throws SQLException { return 0; }
@Override public void setMaxRows(int max) throws SQLException {}
@Override public void setEscapeProcessing(boolean enable) throws SQLException {}
@Override public int getQueryTimeout() throws SQLException { return 0; }
@Override public void setQueryTimeout(int seconds) throws SQLException {}
@Override public void cancel() throws SQLException {}
@Override public SQLWarning getWarnings() throws SQLException { return null; }
@Override public void clearWarnings() throws SQLException {}
@Override public void setCursorName(String name) throws SQLException {}
@Override public boolean execute(String sql) throws SQLException { return false; }
@Override public ResultSet getResultSet() throws SQLException { return null; }
@Override public int getUpdateCount() throws SQLException { return 0; }
@Override public boolean getMoreResults() throws SQLException { return false; }
@Override public void setFetchDirection(int direction) throws SQLException {}
@Override public int getFetchDirection() throws SQLException { return 0; }
@Override public void setFetchSize(int rows) throws SQLException {}
@Override public int getFetchSize() throws SQLException { return 0; }
@Override public int getResultSetConcurrency() throws SQLException { return 0; }
@Override public int getResultSetType() throws SQLException { return 0; }
@Override public void addBatch(String sql) throws SQLException {}
@Override public void clearBatch() throws SQLException {}
@Override public int[] executeBatch() throws SQLException { return new int[0]; }
@Override public Connection getConnection() throws SQLException { return null; }
@Override public boolean getMoreResults(int current) throws SQLException { return false; }
@Override public ResultSet getGeneratedKeys() throws SQLException { return null; }
@Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { return 0; }
@Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { return 0; }
@Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { return 0; }
@Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { return false; }
@Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { return false; }
@Override public boolean execute(String sql, String[] columnNames) throws SQLException { return false; }
@Override public int getResultSetHoldability() throws SQLException { return 0; }
@Override public boolean isClosed() throws SQLException { return false; }
@Override public void setPoolable(boolean poolable) throws SQLException {}
@Override public boolean isPoolable() throws SQLException { return false; }
@Override public void closeOnCompletion() throws SQLException {}
@Override public boolean isCloseOnCompletion() throws SQLException { return false; }
@Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; }
@Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; }
};
}
}

然后你可以编写一个像这样的 Spock 测试:

package de.scrum_master.stackoverflow.q58072937

import org.slf4j.Logger
import spock.lang.Specification

import java.sql.SQLException

class SQLExecutorTest extends Specification {
def test() {
given:
def logger = Mock(Logger)
def originalLogger = SQLExecutor.log
SQLExecutor.log = logger
SQLExecutor sqlExecutor = Spy() {
getSnowflakeStatement() >> {
throw new SQLException("uh-oh")
}
}

when:
sqlExecutor.executeDataLoad("dummy")

then:
def exception = thrown RuntimeException
exception.cause instanceof SQLException
exception.cause.message == "uh-oh"
0 * logger.info(*_)
1 * logger.error(*_)

cleanup:
SQLExecutor.log = originalLogger
}
}

正如我上面所说,记录器上的整个交互测试是可选的,并不是回答您的问题所必需的。我这样做只是为了说明什么是可能的。

我也不喜欢我自己的解决方案,因为你需要

  • 为您的测试类使用 spy 对象,并且
  • 了解 executeDataLoad(String) 的内部实现,即它调用 getSnowflakeStatement() 以便能够 stub 后一个方法并使其生效抛出您想要抛出的异常,以覆盖异常处理程序的执行路径。

另请注意,语句 exception.cause.message == "uh-oh" 并不是真正必要的,因为它只是测试模拟。我把它放在那里只是为了向您展示 mock 的东西是如何工作的。

<小时/>

现在让我们假设我们重构您的类以使 Statement 可注入(inject):

  /*private*/ void executeDataLoad(String sql, Statement snowflakeStatement) {
try {
if (snowflakeStatement == null)
snowflakeStatement = getSnowflakeStatement();
log.info("Importing data into Snowflake");
// (...)

然后,您可以将 getSnowflakeStatement() 设为私有(private)(前提是您可以通过另一种公共(public)方法覆盖该声明)并像这样修改您的测试(删除记录器交互测试,以便专注于我的内容)改变):

package de.scrum_master.stackoverflow.q58072937

import spock.lang.Specification

import java.sql.SQLException
import java.sql.Statement

class SQLExecutorTest extends Specification {
def test() {
given:
def sqlExecutor = new SQLExecutor()
def statement = Mock(Statement) {
executeUpdate(_) >> {
throw new SQLException("uh-oh")
}
}

when:
sqlExecutor.executeDataLoad("dummy", statement)

then:
def exception = thrown RuntimeException
exception.cause instanceof SQLException
}
}

看出区别了吗?您不再需要在被测类上使用 Spy,只需对 使用 MockStub 即可您注入(inject)的语句是为了修改其行为。

我可以说并解释更多,但这个答案不能代替测试教程。

关于java - 如何使用具有简单日志语句的 Spock 测试 Java 中的 catch block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58072937/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com