我有一些正在测试的代码,它调用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,但假设这是可能的。)
I've needed this several times as well. I've put together a small sample below, which you'd want to adjust to your needs. Basically, you create your own Appender and add it to the logger you want. If you'd want to collect everything, the root logger is a good place to start, but you can use a more specific if you'd like. Don't forget to remove the Appender when you're done, otherwise you might create a memory leak. Below I've done it within the test, but setUp or @Before and tearDown or @After might be better places, depending on your needs.
此外,下面的实现将所有内容收集到内存中的List中。如果您记录了很多日志,您可能会考虑添加一个过滤器来删除无聊的条目,或者将日志写入磁盘上的临时文件(提示:LoggingEvent是可序列化的,因此如果您的日志消息是可序列化的,那么您应该能够序列化事件对象)。
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class MyTest {
@Test
public void test() {
final TestAppender appender = new TestAppender();
final Logger logger = Logger.getRootLogger();
logger.addAppender(appender);
try {
Logger.getLogger(MyTest.class).info("Test");
}
finally {
logger.removeAppender(appender);
}
final List<LoggingEvent> log = appender.getLog();
final LoggingEvent firstLogEntry = log.get(0);
assertThat(firstLogEntry.getLevel(), is(Level.INFO));
assertThat((String) firstLogEntry.getMessage(), is("Test"));
assertThat(firstLogEntry.getLoggerName(), is("MyTest"));
}
}
class TestAppender extends AppenderSkeleton {
private final List<LoggingEvent> log = new ArrayList<LoggingEvent>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(final LoggingEvent loggingEvent) {
log.add(loggingEvent);
}
@Override
public void close() {
}
public List<LoggingEvent> getLog() {
return new ArrayList<LoggingEvent>(log);
}
}
I also ran into the same challanged and ended up at this page. Although I am 11 years too late to answers the question, I thought maybe it could be still usefull for others. I found the answer of davidxxx with Logback and the ListAppander very usefull. I used the same configuration for multiple projects, however it was not so fun to copy/paste it and maintaining all the version when I needed to changes something. I thought it would be better to make a library out of it and contribute back to the community. It works with SLFJ4, Log4j, Log4j2, Java Util Logging, JBoss Logging and with Lombok annotations. Please have a look here: LogCaptor for detailed examples and how to add it to your project.
示例情况:
public class FooService {
private static final Logger LOGGER = LoggerFactory.getLogger(FooService.class);
public void sayHello() {
LOGGER.warn("Congratulations, you are pregnant!");
}
}
使用LogCaptor的单元测试示例:
import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class FooServiceTest {
@Test
public void sayHelloShouldLogWarnMessage() {
LogCaptor logCaptor = LogCaptor.forClass(FooService.class);
FooService fooService = new FooService();
fooService.sayHello();
assertThat(logCaptor.getWarnLogs())
.contains("Congratulations, you are pregnant!");
}
}
我不太确定是否应该在这里发布这篇文章,因为这也可以被视为推广“我的库”的一种方式,但我认为这对面临同样挑战的开发人员有帮助。
我为log4j回答了一个类似的问题,请参阅how-can-i-test-with-junit-that-a-warning-was-logged-with-log4
这是更新的Log4j2(用2.11.2测试)和junit 5的示例;
package com.whatever.log;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
class TestLogger {
private TestAppender testAppender;
private LoggerConfig loggerConfig;
private final Logger logger = (Logger)
LogManager.getLogger(ClassUnderTest.class);
@Test
@DisplayName("Test Log Junit5 and log4j2")
void test() {
ClassUnderTest.logMessage();
final LogEvent loggingEvent = testAppender.events.get(0);
//asset equals 1 because log level is info, change it to debug and
//the test will fail
assertTrue(testAppender.events.size()==1,"Unexpected empty log");
assertEquals(Level.INFO,loggingEvent.getLevel(),"Unexpected log level");
assertEquals(loggingEvent.getMessage().toString()
,"Hello Test","Unexpected log message");
}
@BeforeEach
private void setup() {
testAppender = new TestAppender("TestAppender", null);
final LoggerContext context = logger.getContext();
final Configuration configuration = context.getConfiguration();
loggerConfig = configuration.getLoggerConfig(logger.getName());
loggerConfig.setLevel(Level.INFO);
loggerConfig.addAppender(testAppender,Level.INFO,null);
testAppender.start();
context.updateLoggers();
}
@AfterEach
void after(){
testAppender.stop();
loggerConfig.removeAppender("TestAppender");
final LoggerContext context = logger.getContext();
context.updateLoggers();
}
@Plugin( name = "TestAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
static class TestAppender extends AbstractAppender {
List<LogEvent> events = new ArrayList();
protected TestAppender(String name, Filter filter) {
super(name, filter, null);
}
@PluginFactory
public static TestAppender createAppender(
@PluginAttribute("name") String name,
@PluginElement("Filter") Filter filter) {
return new TestAppender(name, filter);
}
@Override
public void append(LogEvent event) {
events.add(event);
}
}
static class ClassUnderTest {
private static final Logger LOGGER = (Logger) LogManager.getLogger(ClassUnderTest.class);
public static void logMessage(){
LOGGER.info("Hello Test");
LOGGER.debug("Hello Test");
}
}
}
使用以下maven依赖项
<dependency>
<artifactId>log4j-core</artifactId>
<packaging>jar</packaging>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
Log4J2的API略有不同。你也可以使用它的async appender。我为此创建了一个锁定的appender:
public static class LatchedAppender extends AbstractAppender implements AutoCloseable {
private final List<LogEvent> messages = new ArrayList<>();
private final CountDownLatch latch;
private final LoggerConfig loggerConfig;
public LatchedAppender(Class<?> classThatLogs, int expectedMessages) {
this(classThatLogs, null, null, expectedMessages);
}
public LatchedAppender(Class<?> classThatLogs, Filter filter, Layout<? extends Serializable> layout, int expectedMessages) {
super(classThatLogs.getName()+"."+"LatchedAppender", filter, layout);
latch = new CountDownLatch(expectedMessages);
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
loggerConfig = config.getLoggerConfig(LogManager.getLogger(classThatLogs).getName());
loggerConfig.addAppender(this, Level.ALL, ThresholdFilter.createFilter(Level.ALL, null, null));
start();
}
@Override
public void append(LogEvent event) {
messages.add(event);
latch.countDown();
}
public List<LogEvent> awaitMessages() throws InterruptedException {
assertTrue(latch.await(10, TimeUnit.SECONDS));
return messages;
}
@Override
public void close() {
stop();
loggerConfig.removeAppender(this.getName());
}
}
像这样使用它:
try (LatchedAppender appender = new LatchedAppender(ClassUnderTest.class, 1)) {
ClassUnderTest.methodThatLogs();
List<LogEvent> events = appender.awaitMessages();
assertEquals(1, events.size());
//more assertions here
}//appender removed