当前位置: 首页 > 知识库问答 >
问题:

使用StaticLoggerBinder对类进行单元测试

司空实
2023-03-14

我有这样一个简单的课程:

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类中的logger是由StaticLoggerBinder提供的,所以我的问题是-如何让它工作,以便那些检查“1*logger.error(u作为字符串)”可以工作?在ErrorLogger类中,我找不到一种恰当的方式来嘲笑那个记录器。我曾考虑过反射,并以某种方式访问它,此外,mockito注入也有一个想法(但如果由于Slf4j注释,该类中甚至不存在对对象的引用,该怎么做!)提前感谢您的反馈和建议。

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

Too few invocations for:

1*logger.error()   (0 invocations)

Unmatched invocations (ordered by similarity):

共有2个答案

子车心思
2023-03-14

如果使用Spring,则可以访问OutputCaptureRule

@Rule
OutputCaptureRule outputCaptureRule = new OutputCaptureRule()

def test(){
outputCaptureRule.getAll().contains("<your test output>")
}
陈誉
2023-03-14

您需要做的是用mock替换@Slf4jAST转换生成的log字段。

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

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

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

因为log字段被声明私有最终,所以用mock替换该值并不容易。它实际上归结为与这里描述的完全相同的问题。此外,此字段的用法在isEnable()方法中包装,因此例如,每次调用log.error(msg)时,它都被替换为:

if (log.isErrorEnabled()) {
    log.error(msg)
}

那么,如何解决这个问题呢?我建议您在groovy issue tracker上注册一个问题,在那里您可以要求对AST转换进行更友好的测试实现。然而,现在这对你没有多大帮助。

你可能会考虑一些解决办法

  1. 在测试中使用上面提到的堆栈溢出问题中描述的“可怕的黑客”设置新字段值。即,使用反射使字段可访问并设置值。请记住在清理期间将该值重置为原始值

我还想指出,您的ErrorLoggerSpec类有几个问题。这些被你已经遇到的问题所隐藏,所以当它们显现出来时,你可能会自己弄清楚。

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

为了隔离黑客攻击,实现简单的重用,避免忘记重置值,我将其编写为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
    }
}
 类似资料:
  • 目前正在搜索教程、解释和示例。我试过不同的例子,遇到了不同的错误。我当前的错误是: |错误编译错误编译[单元]测试:启动失败: 在我的测试报告中。它输出: 单元测试结果-摘要未执行测试。 我的“用户pec.groovy”代码是这样的: 有人能帮忙吗。我是圣杯新手。谢谢 除上述问题外,当我在课堂上省略了如下所示的禁忌: 我发现了这个错误: |运行1单元测试...1 of 1|失败:初始化错误(org

  • 我正在尝试对<code>java进行单元测试。lang类。 背景:到目前为止有效的方法 这不是真正的标准,但是将 } 中 (在 Ubuntu 上)中提供的源代码和 rt.jar 中提供的类复制到 中非常简单,然后 JaCoCo maven 插件可以看到它们并为某些类生成合适的输出。 例如,我得到了一些关于来源的报道。 但是我无法覆盖< code>java.lang中的类。调用< code>Byte

  • 问题内容: 我有几个DAO对象,这些对象用于从数据库中检索信息,我 确实 想为它们编写一些自动化测试,但是我很难弄清楚该怎么做。 我正在使用Spring 来运行实际查询(通过准备好的语句)并将结果映射到模型对象(通过类)。 如果要编写单元测试,则不确定如何/应该模拟对象。例如,由于只有读操作,因此我将使用实际的数据库连接,而不模拟jdbcTemplate,但我不确定那是正确的。 这是批处理中最简单

  • 我有DaoImpl类: 我的测试是: 测试是成功的,但是当我运行具有覆盖率的junit测试时,它显示方法没有被覆盖,因此我的整体单元测试行覆盖率低于要求。我们能涵盖那部分吗?如果是,我们怎么做?谢了。

  • 问题内容: 我选择的数据库是MongoDB。我正在编写一个数据层API,以从客户端应用程序中抽象实现细节- 也就是说,我实质上是在提供一个公共接口(一个充当IDL的对象)。 我正在以TDD方式测试自己的逻辑。在每个单元测试之前,调用一个方法来创建数据库单例,此后,当测试完成时,将调用一个方法来删除数据库。这有助于促进单元测试之间的独立性。 几乎所有单元测试(即 执行上下文查询 )都需要先进行某种插

  • 我想测试以下骆驼路线。我在网上找到的所有例子都有以文件开头的路由,在我的例子中,我有一个Springbean方法,每隔几分钟就会被调用一次,最后消息被转换并移动到jms以及审计目录。 我对这条路线的写测试毫无头绪。目前我在测试用例中所拥有的是