如何使用私有静态最终 Logger 字段对 Java 类进行单元测试

问题描述 投票:0回答:3

我一直在开发一个将 Java 17 与 Spring 5.3、JUnit 5.9 和 Mockito 5.1 结合使用的 Web 应用程序。

在整个代码库中,我经常看到日志字段声明为

private static final
的类。这些字段通常出现在 catch 块中,如下所示,我很难找到在单元测试中直接覆盖它们的方法。

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

public class Yorkie {

    private static final Logger LOGGER = LoggerFactory.getLogger(Yorkie.class);

    private String favoriteToy = "Ball";

    private String fetchFavoriteToy() throws Exception {
      
        if (Math.random() < 0.5) {
            throw new Exception("Favorite toy not found!");
        }
        return favoriteToy;
    }

    private void bark() {
        try {
            String toy = fetchFavoriteToy();
        } catch (Exception e) {
            LOGGER.error(LoggingFacade.formatError("ERROR-Dog", null, null,
                    null, "Unable to bark: " + e.getMessage()));
        }
    }

    public static void main(String[] args) {
        Yorkie yorkie = new Yorkie();
        yorkie.bark();
    }
}

有没有一种安全的方法来严格使用 JUnit 和 Mockito 实际断言

LOGGER
的任何内容?

java junit mockito junit5
3个回答
1
投票

一种解决方法可能是使用自定义附加程序类来记录事件,以便收集和断言。例如,如果 logback 与 slf4j 一起使用,那么:

public class Appender extends AppenderBase<ILoggingEvent> {
    static List<ILoggingEvent> events = new ArrayList<>();

    @Override
    public void append(ILoggingEvent e) {
        events.add(e);
    }

    public static List<ILoggingEvent> getEvents() {
        return events;
    }

    public static void clear() {
        events.clear();
    }
}

然后对于测试用例,可以使用自定义附加程序(配置应位于测试的资源路径中):

<configuration>
    <appender name="appender" class="org.your.package">
    </appender>
    <root level="trace">
        <appender-ref ref="appender"/>
    </root>
</configuration>

在测试本身中:

  List<ILoggingEvent> l = Appender.getEvents()
  // assert

但是访问这个静态附加器会出现竞争情况,这也可以解决。


1
投票

我编写了一个可以执行此操作的库:junit-support

@TestLogger
注释允许您注入记录器上下文,然后可以使用它来配置附加程序、日志级别等。在测试结束时,所有日志记录都将被重置,这使得创建独立测试变得非常容易。

您需要为自己提供的唯一东西是实际的日志记录实现(或者在本例中为 SLF4J 桥):java.util.logging、Log4J 2、logback 或 reload4j 之一。

reload4j 的示例;其他实现的唯一真正区别是要使用的上下文类:

@Test
void testLogging(@TestLogger.ForClass(MyClass.class) Reload4jLoggerContext loggerContext) {
    Appender appender = mock(Appender.class);
    loggerContext
            .setLevel(Level.INFO)
            .useParentAppenders(false)
            .setAppender(appender);

    // perform calls that trigger the logger

    ArgumentCaptor<LoggingEvent> eventCaptor = ArgumentCaptor.forClass(LoggingEvent.class);
    verify(appender, atLeastOnce()).doAppend(eventCaptor.capture());
    List<LoggingEvent> events = eventCaptor.getAllValues();
    // perform assertions on events
}

0
投票

如果删除

final
,则可以使用反射来注入模拟Logger:

private static Logger LOGGER = LoggerFactory.getLogger(Yorkie.class);

然后在你的测试中:

Logger mockLogger = mock(Logger.class);
Yorkie.class.getDeclaredField("LOGGER");
loggerField.setAccessible(true); // undo "private"
loggerField.set(null, mockLogger);

然后你可以断言记录器是如何被调用的:

verify(mockLogger). error(anyString());

在 Java 12 之前,您可以使用反射来更改

final
字段,但在之后则不行。使该字段成为非最终字段没有任何实际好处 - 这主要是一种风格问题。让你的代码可测试胜过风格。

© www.soinside.com 2019 - 2024. All rights reserved.