如何对记录器中的消息执行 JUnit 断言

2022-08-31 05:25:20

我有一些测试代码,它调用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,但假设这是可能的。


答案 1

我也需要过几次。我在下面整理了一个小样本,您希望根据自己的需要进行调整。基本上,您可以创建自己的记录器并将其添加到所需的记录器中。如果您想收集所有内容,根记录器是一个很好的起点,但是如果您愿意,可以使用更具体的记录器。完成后,不要忘记删除追加器,否则可能会造成内存泄漏。下面我已经在测试中完成了它,但是根据您的需求,或者或和/或可能是更好的地方。AppendersetUp@BeforetearDown@After

此外,下面的实现收集内存中的所有内容。如果您要大量记录,则可以考虑添加一个过滤器来删除无聊的条目,或者将日志写入磁盘上的临时文件(提示:是 ,因此,如果您的日志消息是,则应该能够序列化事件对象。ListLoggingEventSerializable

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);
    }
}

答案 2

这是一个简单有效的日志备份解决方案。
它不需要添加/创建任何新类。
它依赖于 ListAppender :一个白盒 logback 追加器,其中日志条目被添加到一个字段中,我们可以用它来进行断言。public List

下面是一个简单的示例。

傅类 :

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 断言听起来不太适合断言列表元素的某些特定属性。
Matcher/assertion库作为AssertJ或Hamcrest似乎更好:

使用 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));

推荐