gpt4 book ai didi

unit-testing - 使用 StaticLoggerBinder 对类进行单元测试

转载 作者:行者123 更新时间:2023-12-04 03:01:22 34 4
gpt4 key购买 nike

我确实有一个像这样的简单类(class):

package com.example.howtomocktest

import groovy.util.logging.Slf4j
import java.nio.channels.NotYetBoundException

@Slf4j
class ErrorLogger {
static void handleExceptions(Closure closure) {
try {
closure()
}catch (UnsupportedOperationException|NotYetBoundException ex) {
log.error ex.message
} catch (Exception ex) {
log.error 'Processing exception {}', ex
}
}
}

我想为它写一个测试,这是一个骨架:
package com.example.howtomocktest

import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static com.example.howtomocktest.ErrorLogger.handleExceptions

class ErrorLoggerSpec extends Specification {

private static final UNSUPPORTED_EXCEPTION = { throw UnsupportedOperationException }
private static final NOT_YET_BOUND = { throw NotYetBoundException }
private static final STANDARD_EXCEPTION = { throw Exception }
private Logger logger = Mock(Logger.class)
def setup() {

}

def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions {UNSUPPORTED_EXCEPTION}

then:
notThrown(UnsupportedOperationException)
1 * logger.error(_ as String) // doesn't work
}

def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions {NOT_YET_BOUND}

then:
notThrown(NotYetBoundException)
1 * logger.error(_ as String) // doesn't work
}

def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions {STANDARD_EXCEPTION}

then:
notThrown(STANDARD_EXCEPTION)
1 * logger.error(_ as String) // doesn't work
}
}

ErrorLogger 类中的记录器由 StaticLoggerBinder 提供,所以我的问题是 - 我如何使它工作,以便那些检查“1 * logger.error(_ as String)”可以工作?我找不到在 ErrorLogger 类中模拟该记录器的正确方法。我已经考虑过反射并以某种方式访问​​它,此外还有一个模拟注入(inject)的想法(但是如果由于 Slf4j 注释,该类中甚至不存在对对象的引用,如何做到这一点!)提前感谢您的所有反馈和建议。

编辑:这是一个测试的输出,即使 1*logger.error(_) 也不起作用。
Too few invocations for:

1*logger.error() (0 invocations)

Unmatched invocations (ordered by similarity):

最佳答案

您需要做的是替换 log @Slf4j 生成的字段使用您的模拟进行 AST 转换。

然而,这并不容易实现,因为生成的代码对测试并不友好。

快速查看生成的代码会发现它对应于以下内容:

class ErrorLogger {
private final static transient org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(ErrorLogger)
}

由于 log字段声明为 private final用你的模拟替换值并不容易。它实际上归结为与 here 描述的完全相同的问题。 .此外,该字段的用法包含在 isEnabled() 中。方法,例如每次调用 log.error(msg)它被替换为:
if (log.isErrorEnabled()) {
log.error(msg)
}

那么,如何解决这个问题呢?我建议您在 groovy issue tracker 上注册问题。 ,您要求对 AST 转换进行更易于测试的实现。但是,现在这对您没有多大帮助。

您可能会考虑一些解决方案。
  • 使用 in the stack overflow question mentioned above 描述的“可怕的 hack”在测试中设置新字段值. IE。使用反射使字段可访问并设置值。请记住在清理期间将值重置为原始值。
  • 添加 getLog()方法到您的ErrorLogger类并使用该方法进行访问,而不是直接访问字段。然后你可以操纵 metaClass覆盖 getLog()执行。这种方法的问题是您必须修改生产代码并添加一个 getter,这违背了使用 @Slf4j 的目的。首先。

  • 我还想指出您的 ErrorLoggerSpec 存在几个问题。类(class)。这些被你已经遇到的问题所掩盖,所以当它们表现出来时,你可能会自己弄清楚。

    尽管这是一个 hack,但我只会为第一个建议提供代码示例,因为第二个建议修改了生产代码。

    为了隔离 hack,实现简单的重用并避免忘记重置值,我将其编写为 JUnit 规则(也可以在 Spock 中使用)。
    import org.junit.rules.ExternalResource
    import org.slf4j.Logger
    import java.lang.reflect.Field
    import java.lang.reflect.Modifier

    public class ReplaceSlf4jLogger extends ExternalResource {
    Field logField
    Logger logger
    Logger originalLogger

    ReplaceSlf4jLogger(Class logClass, Logger logger) {
    logField = logClass.getDeclaredField("log");
    this.logger = logger
    }

    @Override
    protected void before() throws Throwable {
    logField.accessible = true

    Field modifiersField = Field.getDeclaredField("modifiers")
    modifiersField.accessible = true
    modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL)

    originalLogger = (Logger) logField.get(null)
    logField.set(null, logger)
    }

    @Override
    protected void after() {
    logField.set(null, originalLogger)
    }

    }

    这是规范,在修复所有小错误并添加此规则之后。更改在代码中注释:
    import org.junit.Rule
    import org.slf4j.Logger
    import spock.lang.Specification
    import java.nio.channels.NotYetBoundException
    import static ErrorLogger.handleExceptions

    class ErrorLoggerSpec extends Specification {

    // NOTE: These three closures are changed to actually throw new instances of the exceptions
    private static final UNSUPPORTED_EXCEPTION = { throw new UnsupportedOperationException() }
    private static final NOT_YET_BOUND = { throw new NotYetBoundException() }
    private static final STANDARD_EXCEPTION = { throw new Exception() }

    private Logger logger = Mock(Logger.class)

    @Rule ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(ErrorLogger, logger)

    def "Message logged when UnsupportedOperationException is thrown"() {
    when:
    handleExceptions UNSUPPORTED_EXCEPTION // Changed: used to be a closure within a closure!
    then:
    notThrown(UnsupportedOperationException)
    1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
    1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
    }

    def "Message logged when NotYetBoundException is thrown"() {
    when:
    handleExceptions NOT_YET_BOUND // Changed: used to be a closure within a closure!
    then:
    notThrown(NotYetBoundException)
    1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
    1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
    }

    def "Message about processing exception is logged when standard Exception is thrown"() {
    when:
    handleExceptions STANDARD_EXCEPTION // Changed: used to be a closure within a closure!
    then:
    notThrown(Exception) // Changed: you added the closure field instead of the class here
    //1 * logger.isErrorEnabled() >> true // this call is NOT added by the AST transformation -- perhaps a bug?
    1 * logger.error(_ as String, _ as Exception) // in this case, both a message and the exception is specified
    }
    }

    关于unit-testing - 使用 StaticLoggerBinder 对类进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25022453/

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