我一直在开发一个将 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
的任何内容?
一种解决方法可能是使用自定义附加程序类来记录事件,以便收集和断言。例如,如果 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
但是访问这个静态附加器会出现竞争情况,这也可以解决。
我编写了一个可以执行此操作的库: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
}
如果删除
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
字段,但在之后则不行。使该字段成为非最终字段没有任何实际好处 - 这主要是一种风格问题。让你的代码可测试胜过风格。