我有一些正在测试的代码,它调用Java记录器来报告其状态。
在JUnit测试代码中,我想验证在这个日志记录器中创建了正确的日志条目。大致如下:
methodUnderTest(bool x){
if(x)
logger.info("x happened")
}
@Test tester(){
// perhaps setup a logger first.
methodUnderTest(true);
assertXXXXXX(loggedLevel(),Level.INFO);
}
我认为这可以用一个经过特别调整的记录器(或处理程序或格式化程序)来完成,但我更愿意重用现有的解决方案。(而且,老实说,我不清楚如何从记录器获得logRecord,但假设这是可能的。)
这里有一个简单有效的Logback解决方案。
它不需要添加/创建任何新类。
它依赖于ListAppender:一个白盒回logback appender,其中将日志条目添加到公共List字段中,我们可以使用该字段来进行断言。
这里有一个简单的例子。
Foo类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
static final Logger LOGGER = LoggerFactory.getLogger(Foo .class);
public void doThat() {
LOGGER.info("start");
//...
LOGGER.info("finish");
}
}
FooTest类:
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
public class FooTest {
@Test
void doThat() throws Exception {
// get Logback Logger
Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class);
// create and start a ListAppender
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
// add the appender to the logger
// addAppender is outdated now
fooLogger.addAppender(listAppender);
// call method under test
Foo foo = new Foo();
foo.doThat();
// JUnit assertions
List<ILoggingEvent> logsList = listAppender.list;
assertEquals("start", logsList.get(0)
.getMessage());
assertEquals(Level.INFO, logsList.get(0)
.getLevel());
assertEquals("finish", logsList.get(1)
.getMessage());
assertEquals(Level.INFO, logsList.get(1)
.getLevel());
}
}
JUnit断言听起来不太适合断言列表元素的某些特定属性。
像AssertJ或Hamcrest这样的Matcher/断言库似乎更好:
使用AssertJ,它将是:
import org.assertj.core.api.Assertions;
Assertions.assertThat(listAppender.list)
.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
.containsExactly(Tuple.tuple("start", Level.INFO), Tuple.tuple("finish", Level.INFO));
通过添加Appender进行单元测试并不能真正测试Logger的配置。因此,我认为这是一种独特的情况,在这种情况下,单元测试没有带来那么多价值,而集成测试带来了很多价值(特别是如果您的日志有一些审计目的)。
为了为它创建集成测试,让我们假设您正在运行一个简单的ConsoleAppender,并希望测试它的输出。然后,您应该测试如何将消息从System.out写入到它自己的ByteArrayOutputStream。
从这个意义上说,我会做以下事情(我使用JUnit 5):
public class Slf4jAuditLoggerTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
@BeforeEach
public void beforeEach() {
System.setOut(new PrintStream(outContent));
}
通过这种方式,你可以简单地测试它的输出:
@Test
public void myTest() {
// Given...
// When...
// Then
assertTrue(outContent.toString().contains("[INFO] My formatted string from Logger"));
}
如果你这样做了,你将为你的项目带来更多的价值,而不需要使用内存中的实现,创建一个新的Appender,或者其他什么。
请注意,在Log4J 2中。X,公共接口org.apache.logging.log4j。Logger不包括setAppender()和removeAppender()方法。
但是,如果您没有做太花哨的事情,您应该能够将它强制转换为实现类org.apache.logging.log4j.core。记录器,它公开了这些方法。
这里有一个Mockito和AssertJ的例子:
// Import the implementation class rather than the API interface
import org.apache.logging.log4j.core.Logger;
// Cast logger to implementation class to get access to setAppender/removeAppender
Logger log = (Logger) LogManager.getLogger(MyClassUnderTest.class);
// Set up the mock appender, stubbing some methods Log4J needs internally
Appender appender = mock(Appender.class);
when(appender.getName()).thenReturn("Mock Appender");
when(appender.isStarted()).thenReturn(true);
log.addAppender(appender);
try {
new MyClassUnderTest().doSomethingThatShouldLogAnError();
} finally {
log.removeAppender(appender);
}
// Verify that we got an error with the expected message
ArgumentCaptor<LogEvent> logEventCaptor = ArgumentCaptor.forClass(LogEvent.class);
verify(appender).append(logEventCaptor.capture());
LogEvent logEvent = logEventCaptor.getValue();
assertThat(logEvent.getLevel()).isEqualTo(Level.ERROR);
assertThat(logEvent.getMessage().getFormattedMessage()).contains(expectedErrorMessage);