如何在JUnit单元测试中测试slf4j-api和logback日志
1. 概述
在本文中,我们将学习如何使用JUnit测试日志记录的生成
我们将使用slf4j-api和logback作为日志框架,并创建一个可用于日志断言的自定义appender
2. Maven 依赖
在开始之前,让我们添加logback依赖项。由于logback本身也实现了slf4j-api接口,log4j也会自动下载到项目中:
如果是spring boot 项目,由于默认日志框架是logback则不需要单独下载依赖项
dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>.
<version>1.2.3</version>
</dependency>
AssertJ在测试时提供了非常有用的断言功能,下面让我们把它也添加到项目中:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
3. 一个简单的业务函数
现在,让我们创建一个对象,它将生成用于测试的日志。
创建的BusinessWorker对象将只公开一个方法。此方法将为每个日志级别生成具有相同内容的日志。虽然这个方法实际上不是很有用,但它可以很好地满足我们的测试目的:
public class BusinessWorker {
private static Logger LOGGER = LoggerFactory.getLogger(BusinessWorker.class);
public void generateLogs(String msg) {
LOGGER.trace(msg);
LOGGER.debug(msg);
LOGGER.info(msg);
LOGGER.warn(msg);
LOGGER.error(msg);
}
}
4. 测试我们的日志输出
如果要生成日志,在src/test/resources/文件夹中创建一个logback.xml文件。这只是一个最简单的日志配置,并重定向所有日志到一个控制台appender:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<root level="error">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
5. 创建MemoryAppender让日志输出到内存中
现在,让我们创建一个在内存中保存日志的自定义appender。我们将扩展logback提供的ListAppender,并使用一些有用的方法来扩展它的功能:
public class MemoryAppender extends ListAppender<ILoggingEvent> {
public void reset() {
this.list.clear();
}
public boolean contains(String string, Level level) {
return this.list.stream()
.anyMatch(event -> event.getMessage().toString().contains(string)
&& event.getLevel().equals(level));
}
public int countEventsForLogger(String loggerName) {
return (int) this.list.stream()
.filter(event -> event.getLoggerName().contains(loggerName))
.count();
}
public List<ILoggingEvent> search(String string) {
return this.list.stream()
.filter(event -> event.getMessage().toString().contains(string))
.collect(Collectors.toList());
}
public List<ILoggingEvent> search(String string, Level level) {
return this.list.stream()
.filter(event -> event.getMessage().toString().contains(string)
&& event.getLevel().equals(level))
.collect(Collectors.toList());
}
public int getSize() {
return this.list.size();
}
public List<ILoggingEvent> getLoggedEvents() {
return Collections.unmodifiableList(this.list);
}
}
MemoryAppender 接收日志,并存储日志信息到内存中,并提供如下方法用于测试
- reset() : 清除列表
- contains(msg, level) :只有当列表中包含一个与指定内容和严重性级别匹配的ILoggingEvent时,才返回true
- counteventforlogger (loggerName) : 返回由指定日志记录器生成的ILoggingEvent的数量返回与特定内容匹配的ILoggingEvent列表
- search(msg, level) : 返回与指定内容和严重性级别匹配的ILoggingEvent列表
- getSize() :返回ILoggingEvents的数量
- getLoggedEvents() :返回ILoggingEvent元素,不可修改
6. Unit Test 单元测试
接下来,让我们为BusinessWorker创建一个JUnit测试
我们将把MemoryAppender声明为类的一个属性,并通过编码的方式将其加入到日志系统中,并指定它为日志输出appender
初始化日志框架
@Before
public void setup() {
Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME);
memoryAppender = new MemoryAppender();
memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
logger.setLevel(Level.DEBUG);
logger.addAppender(memoryAppender);
memoryAppender.start();
}
现在我们可以创建一个简单的测试,在其中实例化BusinessWorker类并调用generateLogs方法生成日志,然后我们可以断言它生成的日志:
@Test
public void test() {
BusinessWorker worker = new BusinessWorker();
worker.generateLogs(MSG);
assertThat(memoryAppender.countEventsForLogger(LOGGER_NAME)).isEqualTo(4);
assertThat(memoryAppender.search(MSG, Level.INFO).size()).isEqualTo(1);
assertThat(memoryAppender.contains(MSG, Level.TRACE)).isFalse();
}
这个测试使用MemoryAppender的三个特性:
- 已经生成了四个日志—每个严重性应该有一个条目,并过滤了跟踪级别
- 只有一个INFO级别的日志消息
- 没有TRACE级别的日志消息
如果在测试中产生大量的日志消息,那么内存使用量将会上升。我们可以在每次测试之前调用MemoryAppender.clear()方法来释放内存并避免OutOfMemoryException
7. 结论
在本文中,我们介绍了如何在单元测试中测试生成日志