我正在使用slf4j,想要对我的代码进行单元测试,以确保在特定条件下生成warn/error日志消息。我希望这些测试是严格的单元测试,因此我不想从文件中调用日志配置来测试日志消息是否生成。我使用的模拟框架是Mockito。
我正在使用slf4j,想要对我的代码进行单元测试,以确保在特定条件下生成warn/error日志消息。我希望这些测试是严格的单元测试,因此我不想从文件中调用日志配置来测试日志消息是否生成。我使用的模拟框架是Mockito。
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());
}
}
然后使用它:
@Rule
public final LoggerRule loggerRule = new LoggerRule();
@Test
public void yourTest() {
// ...
assertThat(loggerRule.getFormattedMessages().size()).isEqualTo(2);
}
----- JUnit 5扩展Oct 2021 -----
LogCapture:
public class LogCapture {
private ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
LogCapture() {
}
public String getFirstFormattedMessage() {
return getFormattedMessageAt(0);
}
public String getLastFormattedMessage() {
return getFormattedMessageAt(listAppender.list.size() - 1);
}
public String getFormattedMessageAt(int index) {
return getLoggingEventAt(index).getFormattedMessage();
}
public LoggingEvent getLoggingEvent() {
return getLoggingEventAt(0);
}
public LoggingEvent getLoggingEventAt(int index) {
return (LoggingEvent) listAppender.list.get(index);
}
public List<LoggingEvent> getLoggingEvents() {
return listAppender.list.stream().map(e -> (LoggingEvent) e).collect(Collectors.toList());
}
public void setLogFilter(Level logLevel) {
listAppender.clearAllFilters();
listAppender.addFilter(buildLevelFilter(logLevel));
}
public void clear() {
listAppender.list.clear();
}
void start() {
setLogFilter(Level.INFO);
listAppender.start();
}
void stop() {
if (listAppender == null) {
return;
}
listAppender.stop();
listAppender.list.clear();
listAppender = null;
}
ListAppender<ILoggingEvent> getListAppender() {
return listAppender;
}
private Filter<ILoggingEvent> buildLevelFilter(Level logLevel) {
LevelFilter levelFilter = new LevelFilter();
levelFilter.setLevel(logLevel);
levelFilter.setOnMismatch(FilterReply.DENY);
levelFilter.start();
return levelFilter;
}
}
日志捕获扩展:
public class LogCaptureExtension implements ParameterResolver, AfterTestExecutionCallback {
private Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
private LogCapture logCapture;
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == LogCapture.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
logCapture = new LogCapture();
setup();
return logCapture;
}
@Override
public void afterTestExecution(ExtensionContext context) {
teardown();
}
private void setup() {
logger.addAppender(logCapture.getListAppender());
logCapture.start();
}
private void teardown() {
if (logCapture == null || logger == null) {
return;
}
logger.detachAndStopAllAppenders();
logCapture.stop();
}
}
然后使用它:
@ExtendWith(LogCaptureExtension.class)
public class SomeTest {
@Test
public void sometest(LogCapture logCapture) {
// do test here
assertThat(logCapture.getLoggingEvents()).isEmpty();
}
// ...
}
如果想要在不依赖于特定实现(如log4j)的情况下测试slf4j,可以按照SLF4J FAQ中所描述的方法提供自己的slf4j日志实现。您的实现可以记录已记录的消息,然后由单元测试进行验证。
slf4j-test软件包正是这样做的。它是一个内存中的slf4j日志实现,提供了检索记录消息的方法。
slf4j-test
包可以在此处找到:https://github.com/jaegertracing/jaeger-client-java/pull/378/files。不可否认,他们的文档也非常出色。 - Debosmit Ray我认为您可以通过自定义appender来解决您的问题。创建一个实现org.apache.log4j.Appender
的测试appender,并在log4j.properties
中设置您的appender并在执行测试用例时加载它。
如果从那个appender
回调到测试框架,则可以检查记录的消息。
为了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());
}
}
然后使用它:
@RegisterExtension
public LoggerExtension loggerExtension = new LoggerExtension();
@Test
public void yourTest() {
// ...
assertThat(loggerExtension.getFormattedMessages().size()).isEqualTo(2);
}
private static final Logger LOGGER = LoggerFactory.getLogger(Example.class)
。该工具无法处理常量记录器,因此无法处理正常的使用情况。 - Sven Döringpublic static final String ERROR_MSG_LOGGER_IS_NULL = "Logger is null";
private Logger logger;
@Inject
public MyThing() {
this(LoggerFactory.getLogger(MyThing.class));
}
public MyThing(final Logger lgr) {
if (null == lgr) {
throw new IllegalArgumentException(ERROR_MSG_LOGGER_IS_NULL);
}
this.logger = lgr;
}
} - granadaCoder与其嘲弄SLF4J,不如将你需要测试的重要日志调用放在它们自己的方法中,这样你就可以更轻松地进行模拟。
如果你真的想要模拟SLF4J,我敢打赌你可以创建自己的提供者来允许你从SLF4J方面提供一个伪造记录器,而不是将其注入到你的服务对象中。
<dependency>
<groupId>uk.org.lidalia</groupId>
<artifactId>slf4j-test</artifactId>
<version>1.2.0</version>
</dependency>
样例类
@Slf4j
public class SampleClass {
public void logDetails(){
log.info("Logging");
}
}
测试类
import org.junit.Test;
import uk.org.lidalia.slf4jtest.TestLogger;
import uk.org.lidalia.slf4jtest.TestLoggerFactory;
import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static uk.org.lidalia.slf4jtest.LoggingEvent.info;
public class SampleClassTest {
TestLogger logger = TestLoggerFactory.getTestLogger(SampleClass.class);
@Test
public void testLogging(){
SampleClass sampleClass = new SampleClass();
//Invoke slf4j logger
sampleClass.logDetails();
assertThat(logger.getLoggingEvents(), is(asList(info("Logging"))));
}
}
ImmutableList
- Ashish Sharma和@Zsolt类似,您可以模拟log4j Appender
并将其设置在Logger
上,然后验证对Appender.doAppend()
的调用。这样可以在不修改真实代码的情况下进行测试。
import static org.assertj.core.api.Assertions.assertThat;
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.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
public class LoggingTest {
private final ClassToTest sut = new ClassToTest();
private ListAppender<ILoggingEvent> listAppender;
@BeforeEach
void init() {
final var log = (Logger) LoggerFactory.getLogger(ClassToTest.class);
listAppender = new ListAppender<>();
listAppender.start();
log.addAppender(listAppender);
}
@Test
public void testLogging() {
sut.doSomethingThatLogs()
String message = listAppender.list.get(0).getFormattedMessage();
assertThat(message).contains("this message should be logged");
}
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.Logger;
@RunWith(MockitoJUnitRunner.class)
public class JUnit4ExampleTest {
private static final String INFO_TEST_MESSAGE = "info log test message from JUnit4";
@Mock
Logger logger;
@InjectMocks
Example sut;
@Test
public void logInfoShouldBeLogged() {
// when
sut.methodWithLogInfo(INFO_TEST_MESSAGE);
// then
Mockito.verify(logger).info(INFO_TEST_MESSAGE);
Mockito.verifyNoMoreInteractions(logger);
}
}
正如您所见,测试代码中不需要任何特殊步骤。您只需在项目中添加库的依赖项。
更多示例和说明请参见: