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

Spock单元测试断言日志调用并查看输出

龙俊良
2023-03-14

我正在使用spock测试Java Spring Boot代码。它在lombok@slf4j注释上获得一个日志记录器。

具有日志调用的虚拟类

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Clazz {

  public void method() {
    // ... code
    log.warn("message", new RuntimeException());
  }
}

斯波克规格

import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification

@Slf4j
class LogSpec extends Specification {

  Clazz clazz = new Clazz()

  private Logger logger = Mock(Logger.class)

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

  def "warning ia logged"() {

    given: "expected message"

    when: "when calling the method"
    clazz.method()

    then: "a warning is logged"
    1 * logger.warn(_, _) >> {
      msg, ex -> log.warn(msg, ex)
    }
  }
}

用模拟记录器从这个答案切换真实的帮助器。

import org.junit.rules.ExternalResource
import org.slf4j.Logger

import java.lang.reflect.Field
import java.lang.reflect.Modifier

/**
 *  Helper to exchange loggers set by lombok with mock logger
 *
 * allows to assert log action.
 *
 * Undos change after test to keep normal logging in other tests.
 *
 * code from this  <a href="https://stackoverflow.com/a/25031713/3573038">answer</a> answer
 */
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)
  }
}

我想测试日志调用,但仍然看到日志消息。

我提出了这个解决方案,它使用groovy规范的记录器进行调用。

 1 * logger.warn(_ , _) >> {
   msg, ex -> log.warn(msg, ex)
 }

但是我发现它很冗长,不管我如何为它创建一个帮助器函数。我不是很熟悉函数groovy,将此代码移到函数中是行不通的。

我还尝试了一个间谍而不是一个Mock,但这给我带来了一个错误,因为记录器类是final。

  import ch.qos.logback.classic.Logger  

  private Logger logger = Spy(Logger.class)

>> org.spockframework.mock.CannotCreateMockException: Cannot create mock 
for class ch.qos.logback.classic.Logger because Java mocks cannot mock final classes. 
If the code under test is written in Groovy, use a Groovy mock.

运行时记录器类

package ch.qos.logback.classic;

public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {

谢谢

共有1个答案

苍恩
2023-03-14

实际上,在MCVE中,您希望用两个参数调用warn(_,_)方法,但您并不像在clazz中那样进行日志记录,因此您必须更改clazz以也记录异常,或者更改测试以期望用一个参数调用方法。我在这里做后者。

至于你的问题,解决办法是不用模拟而是用间谍。不过,你得告诉斯波克你想监视哪个班级。当然,这是因为您不能监视接口类型。我选择了SimpleLogger(更改应用程序中使用的任何内容)。

package de.scrum_master.stackoverflow

import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.impl.SimpleLogger
import spock.lang.Specification

@Slf4j
class LombokSlf4jLogTest extends Specification {
  SimpleLogger logger = Spy(constructorArgs: ["LombokSlf4jLogTest"])

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

  def "warning is logged"() {
    when: "when calling the method"
    new Clazz().method()

    then: "a warning is logged"
    1 * logger.warn(_)
  }
}

更新:值得注意的是,这里有一个版本,它在类路径上也使用LogBack-Classic而不是Log4J-Simple。我们不直接监视最后一个类,而是监视一个Groovy@delegate:

还请注意,我在测试中更改为*_,以便适应具有任意数量参数的Warn调用。

package de.scrum_master.stackoverflow

import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification

@Slf4j
class LombokSlf4jLogTest extends Specification {
  def logger = Spy(new LoggerDelegate(originalLogger: log))

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

  def "warning is logged"() {
    when: "when calling the method"
    new Clazz().method()

    then: "a warning is logged"
    1 * logger.warn(*_)
    true
  }

  static class LoggerDelegate {
    @Delegate Logger originalLogger
  }
}

更新2020-01-23:我刚刚又找到了这个,并注意到我忘记解释@delegate解决方案的工作原理:因为Groovy委托自动实现所有接口,默认情况下委托实例的类也实现这些接口。在本例中,logger字段声明logger,这是一种接口类型。这也是为什么可以根据配置使用Log4J或Logback实例的原因。在这种情况下,嘲笑或监视未实现接口或与其类名显式使用的最终类类型的伎俩是行不通的,因为委托类不会(也不可能)是最终类类型的子类,因此不能代替委托而被注入。

更新2020-04-14:我之前没有提到过,如果您不想监视一个真正的记录器,而是简单地使用一个可以检查交互的虚拟记录器,只需在org.slf4j.logger接口上使用一个常规的Spock mock:def logger=mock(logger)这实际上是最简单的解决方案,您不会用异常堆栈跟踪和其他日志输出来扰乱测试日志。我太专注于帮助他的间谍解决方案,所以我以前没有提到这一点。

 类似资料:
  • 除了统一的HTML或XML报告之外,我如何获得Spock测试执行控制台输出或类似于我的应用程序日志? 使用Gradle2.11和Log4j支持的Slf4j。

  • 问题内容: 假设我在Python单元测试中具有以下代码: 有没有一种简单的方法可以断言在测试的第二行期间调用了特定方法(在我的情况下)?例如是否有这样的事情: 问题答案: 我为此使用Mock(在py3.3 +上现在是unittest.mock): 对于您的情况,它可能看起来像这样: Mock支持许多有用的功能,包括修补对象或模块的方式以及检查是否调用了正确的东西等。 买者自负! (请当心!) 如果

  • 问题内容: 我有一个Python 2.7方法,有时会调用 当满足正确的条件时,是否可以进行单元测试以验证是否调用了此代码行? 问题答案: 是。加注,因此您可以使用以下命令进行检查: 的实例具有设置为建议的退出状态的属性,并且返回的上下文管理器将捕获的异常实例设置为,因此检查退出状态很容易: sys.exit文档: 从Python退出。这是通过引发异常来实现的…可以在外部级别拦截出口尝试。

  • 玩弄Mockito来实现我的服务的单元测试,但由于某种原因,我无法通过我的厚脑袋来实现这一点。我的考试通过了,但我不能确信我做得对。 下面是一个测试count()方法的示例。该方法只是将调用转发到它的存储库,我不想验证仅此而已,没有其他事情发生。这就是我得到的: 我的考试及格了,但我有一些问题。 > 我需要验证吗?我觉得我这样做是因为我想验证personRepository。实际上调用了count

  • 本文档介绍如何查看 TiDB 集群各组件日志,以及 TiDB 慢查询日志。 TiDB 集群各组件日志 通过 TiDB Operator 部署的 TiDB 各组件默认将日志输出在容器的 stdout 和 stderr 中。可以通过下面的方法查看单个 Pod 的日志: kubectl logs -n ${namespace} ${pod_name} 如果这个 Pod 由多个 Container 组成,

  • 如果它没有返回任何东西,是否可能以某种方式测试它是否停止了这个if语句?我的意思是,我想把某种断言,检查“好吧,上下文是空的,所以它在这个返回时停止了”。 当然,问题是类是空的,为什么我想测试这种方法可能会有问题,但我想听听一些可能性。