gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 22:52:43 28 4
gpt4 key购买 nike

我正在使用 spock 来测试 Java Spring Boot 代码。它通过 lombok @Slf4j 注释获得一个 logback 记录器。

虚拟类带日志调用

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

@Slf4j
@Component
public class Clazz {

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

Spock 规范
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)
}
}
}

helper 使用取自 this answer 的模拟记录器切换真实数据.
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)
}
}

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

我正在使用 this answer 的解决方案,它适用于断言,但我没有看到日志,因为它是一个模拟调用。

我想出了这个解决方案,它使用 groovy 规范的记录器进行调用。
 1 * logger.warn(_ , _) >> {
msg, ex -> log.warn(msg, ex)
}

但我觉得它很冗长,不知道如何为它创建一个辅助函数。我对功能性 groovy 不是很熟悉,并且将此代码移动到一个函数中是行不通的。

我还尝试了 Spy 而不是 Mock 但这让我出错,因为记录器类是最终的。
  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 {

谢谢

最佳答案

其实在你的MCVE您期待 warn(_, _)要使用两个参数调用的方法,但您没有像在 Clazz 中那样记录,所以要么你必须改变Clazz还记录异常或更改测试以期望使用一个参数进行方法调用。我在这里做后者。

至于您的问题,解决方案是不使用模拟,而是使用 spy 。不过,你需要告诉 Spock 你想监视哪个类。这是因为您当然不能监视接口(interface)类型。我选择了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。与其直接监视 final 类,不如监视 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 年 1 月 23 日更新:我刚刚又找到了这个,发现我忘了解释为什么 @Delegate解决方案有效:因为 Groovy 委托(delegate)会自动实现委托(delegate)实例的类默认也实现的所有接口(interface)。在这种情况下,记录器字段被声明为 Logger这是一个接口(interface)类型。这也是为什么例如可以根据配置使用 Log4J 或 Logback 实例。在这种情况下,模拟或监视未实现接口(interface)或显式使用其类名的最终类类型的技巧将不起作用,因为委托(delegate)类不会(也不能)是最终类类型的子类,因此可以不被注入(inject)而不是委托(delegate)。

2020 年 4 月 14 日更新:我之前没有提到,如果你不想监视一个真正的记录器,而只是使用一个可以检查交互的假人,只需在 org.slf4j.Logger 上使用常规的 Spock 模拟。接口(interface): def logger = Mock(Logger)这实际上是最简单的解决方案,并且您不会将测试日志与异常堆栈跟踪和其他日志输出混为一谈。我非常专注于帮助 OP 提供他的 spy 解决方案,以至于我之前没有提到这一点。

关于unit-testing - Spock 单元测试断言日志调用并查看输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49771913/

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