我有一些正在测试的代码,它调用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所做的。

我创建了一个TestAppender类:

public class TestAppender extends AppenderBase<ILoggingEvent> {

    private Stack<ILoggingEvent> events = new Stack<ILoggingEvent>();

    @Override
    protected void append(ILoggingEvent event) {
        events.add(event);
    }

    public void clear() {
        events.clear();
    }

    public ILoggingEvent getLastEvent() {
        return events.pop();
    }
}

然后在我的testng单元测试类的父类中创建了一个方法:

protected TestAppender testAppender;

@BeforeClass
public void setupLogsForTesting() {
    Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    testAppender = (TestAppender)root.getAppender("TEST");
    if (testAppender != null) {
        testAppender.clear();
    }
}

我在src/test/resources中定义了一个logback-test.xml文件,并添加了一个测试appender:

<appender name="TEST" class="com.intuit.icn.TestAppender">
    <encoder>
        <pattern>%m%n</pattern>
    </encoder>
</appender>

并将这个appender添加到根appender:

<root>
    <level value="error" />
    <appender-ref ref="STDOUT" />
    <appender-ref ref="TEST" />
</root>

现在,在从父测试类扩展而来的测试类中,我可以获得appender并记录最后一条消息,并验证消息、级别和throwable。

ILoggingEvent lastEvent = testAppender.getLastEvent();
assertEquals(lastEvent.getMessage(), "...");
assertEquals(lastEvent.getLevel(), Level.WARN);
assertEquals(lastEvent.getThrowableProxy().getMessage(), "...");

其他回答

正如前面提到的,您可以使用mock框架。为此,您必须在类中公开记录器(尽管我可能更倾向于使其包私有而不是创建公共setter)。

另一种解决方案是手工创建一个假记录器。您必须编写伪记录器(更多的fixture代码),但在这种情况下,我更喜欢使用模拟框架中保存的代码来增强测试的可读性。

我会这样做:

class FakeLogger implements ILogger {
    public List<String> infos = new ArrayList<String>();
    public List<String> errors = new ArrayList<String>();

    public void info(String message) {
        infos.add(message);
    }

    public void error(String message) {
        errors.add(message);
    }
}

class TestMyClass {
    private MyClass myClass;        
    private FakeLogger logger;        

    @Before
    public void setUp() throws Exception {
        myClass = new MyClass();
        logger = new FakeLogger();
        myClass.logger = logger;
    }

    @Test
    public void testMyMethod() {
        myClass.myMethod(true);

        assertEquals(1, logger.infos.size());
    }
}

这里有一个很好的和优雅的方法来解决这个问题: https://www.baeldung.com/junit-asserting-logs

Here is the sample code to mock log, irrespective of the version used for junit or sping, springboot.

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;
import org.mockito.ArgumentMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Test;

import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class MyTest {
  private static Logger logger = LoggerFactory.getLogger(MyTest.class);

    @Test
    public void testSomething() {
    ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
    final Appender mockAppender = mock(Appender.class);
    when(mockAppender.getName()).thenReturn("MOCK");
    root.addAppender(mockAppender);

    //... do whatever you need to trigger the log

    verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
      @Override
      public boolean matches(final Object argument) {
        return ((LoggingEvent)argument).getFormattedMessage().contains("Hey this is the message I want to see");
      }
    }));
  }
}

检查这个库https://github.com/Hakky54/log-captor

在maven文件中包含库的引用:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>logcaptor</artifactId>
    <version>2.5.0</version>
    <scope>test</scope>
</dependency>

在java代码测试方法中,你应该包括以下内容:

LogCaptor logCaptor = LogCaptor.forClass(MyClass.class);

 // do the test logic....

assertThat(logCaptor.getLogs()).contains("Some log to assert");

在我的案例中,我解决了如下的问题:

Logger root = (Logger) LoggerFactory.getLogger(CSVTasklet.class); //CSVTasklet is my target class
    final Appender mockAppender = mock(Appender.class);
    root.addAppender(mockAppender); 

verify(mockAppender).doAppend(argThat((ArgumentMatcher) argument -> ((LoggingEvent) argument).getMessage().contains("No projects."))); // I checked "No projects." in the log