对SLF4J日志消息进行单元测试的最佳方法是什么?

Jav*_*mae 37 unit-testing mocking slf4j mockito

我正在使用slf4j,我想对我的代码进行单元测试,以确保在某些条件下生成警告/错误日志消息.我宁愿这些是严格的单元测试,所以我不想从文件中提取日志配置以测试日志消息的生成.我正在使用的模拟框架是Mockito.

eak*_*st7 18

为了在不依赖于特定实现(例如log4j)的情况下测试slf4j,您可以提供自己的slf4j日志记录实现,如本SLF4J FAQ中所述.您的实现可以记录已记录的消息,然后由单元测试进行查询以进行验证.

SLF4J测试包正是这样做的.它是一个内存中的slf4j日志记录实现,它提供了检索记录消息的方法.

  • 这不再适用于 slf4j-api 版本 1.8 或更高版本,因为:“规划 Jigsaw (Java 9) 的出现,slf4j-api 版本 1.8.x 及更高版本使用 ServiceLoader 机制。早期版本的 SLF4J 依赖于静态绑定器机制slf4j-api 不再尊重这一点。” (参见 https://www.slf4j.org/codes.html)。 (4认同)

Zso*_*olt 12

我认为您可以使用自定义appender解决您的问题.创建一个测试appender来实现org.apache.log4j.Appender,并在log4j.properties执行测试用例时设置你的appender 并加载它.

如果从中回调测试线束,则appender可以检查记录的消息

  • 如果您能提供一些代码示例,将不胜感激. (9认同)
  • 但这个例子似乎不适用于 slf4j! (3认同)

And*_*eng 10

创建测试规则:

    import ch.qos.logback.classic.Logger;
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.core.read.ListAppender;
    import org.junit.rules.TestRule;
    import org.junit.runner.Description;
    import org.junit.runners.model.Statement;
    import org.slf4j.LoggerFactory;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class LoggerRule implements TestRule {
    
      private final ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
      private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    
      @Override
      public Statement apply(Statement base, Description description) {
        return new Statement() {
          @Override
          public void evaluate() throws Throwable {
            setup();
            base.evaluate();
            teardown();
          }
        };
      }
    
      private void setup() {
        logger.addAppender(listAppender);
        listAppender.start();
      }
    
      private void teardown() {
        listAppender.stop();
        listAppender.list.clear();
        logger.detachAppender(listAppender);
      }
    
      public List<String> getMessages() {
        return listAppender.list.stream().map(e -> e.getMessage()).collect(Collectors.toList());
      }
    
      public List<String> getFormattedMessages() {
        return listAppender.list.stream().map(e -> e.getFormattedMessage()).collect(Collectors.toList());
      }
    
    }
Run Code Online (Sandbox Code Playgroud)

然后使用它:

    @Rule
    public final LoggerRule loggerRule = new LoggerRule();
    
    @Test
    public void yourTest() {
        // ...
        assertThat(loggerRule.getFormattedMessages().size()).isEqualTo(2);
    }
Run Code Online (Sandbox Code Playgroud)


小智 9

SLF4J的更好的测试实现在并发测试执行的环境中非常有效,这是https://github.com/portingle/slf4jtesting

在谈到并发测试执行时,我已经讨论了一些关于slf4j日志测试的讨论以及现有测试方法的局限性.

我决定把我的话放到代码中,git repo就是结果.

  • 我认为,这不是一个好的实用程序。仅当 Logger 在构造函数中创建时才有效。但谁这么做呢?Logger 通常是一个常量:``private static final Logger LOGGER = LoggerFactory.getLogger(Example.class)``。该实用程序无法处理常量记录器,因此无法处理正常用例。 (2认同)

Dav*_*ess 5

您可以将需要测试的重要日志记录调用放在它们自己的方法中,而不是模拟 SLF4J,您可以更轻松地模拟这些方法。

如果您确实想模拟 SLF4J,我敢打赌您可以为其创建自己的提供程序,该提供程序将允许您从 SLF4J 端提供模拟记录器,而不是在服务对象中注入一个记录器。


小智 5

对于 JUnit 5,创建一个扩展来实现上面创建测试规则中andrew-feng提供的解决方案:

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.stream.Collectors;

public class LoggerExtension implements BeforeEachCallback, AfterEachCallback {

    private final ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
    private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        listAppender.stop();
        listAppender.list.clear();
        logger.detachAppender(listAppender);
    }

    @Override
    public void beforeEach(ExtensionContext extensionContext) throws Exception {
        logger.addAppender(listAppender);
        listAppender.start();
    }

    public List<String> getMessages() {
        return listAppender.list.stream().map(e -> e.getMessage()).collect(Collectors.toList());
    }

    public List<String> getFormattedMessages() {
        return listAppender.list.stream().map(e -> e.getFormattedMessage()).collect(Collectors.toList());
    }

}
Run Code Online (Sandbox Code Playgroud)

然后使用它:

@RegisterExtension
public LoggerExtension loggerExtension = new LoggerExtension();

@Test
public void yourTest() {
    // ...
    assertThat(loggerExtension.getFormattedMessages().size()).isEqualTo(2);
}
Run Code Online (Sandbox Code Playgroud)

  • 或者您可以在测试开始时使用以下语句: Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); ListAppender&lt;ILoggingEvent&gt; listAppender = new ListAppender&lt;&gt;(); logger.addAppender(listAppender); listAppender.start(); 然后在测试结束时断言:List&lt;ILoggingEvent&gt; messagesList = listAppender.list; assertEquals(...,logsList.get(1)) (3认同)