单元测试中验证日志

一般来说,日志是程序相当次要的副作用输出,很少需要专门的单元测试来保证它的行为。不过也不排除在某些情况下需要在单元测试中验证日志,比如:

  • 某个场景下日志输出是下游模块的关键信息。一般出现在错误处理分支中。
  • 某个模块除了日志没有显著的输出来检验它的行为。红灯,模块可测试性差的表现。

这里介绍以比较常用的logbacklog4j2为例介绍验证日志的方法,以备不时之需。

被测试代码

public class SampleService {
    private static Logger LOG = LoggerFactory.getLogger(SampleService.class);
    @Autowired SampleDependency dependency;

    public String foo() {
        LOG.info("starting foo");
        String bar = "";
        try {
            bar = dependency.getExternalValue("bar");
        } catch (Exception e) {
            System.out.println(LOG.getClass().getTypeName());
            LOG.error("calling foo error", e);
        }
        return bar;
    }
}

因为两种框架外加slf4j中有很多同名的类,示例代码中全部带上完整包名以免混淆。

Logback

@Test
public void testLogback() {
    //given
    ch.qos.logback.core.Appender<ch.qos.logback.classic.spi.ILoggingEvent> appender = mock(ch.qos.logback.core.Appender.class);
    ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(SampleService.class)).addAppender(appender);
    doThrow(new RuntimeException("something wrong...")).when(dependency).getExternalValue(anyString());

    //when
    service.foo();

    //then
    ArgumentCaptor<ch.qos.logback.classic.spi.ILoggingEvent> logCaptor = ArgumentCaptor.forClass(ch.qos.logback.classic.spi.ILoggingEvent.class);
    //通过ArgumentCaptor捕获所有log,
    //verify默认是一次调用,改为atLeast(0)让任意次数记录log都能验证通过
    verify(appender, atLeast(0)).doAppend(logCaptor.capture()); 
    logCaptor.getAllValues().stream()
            .filter(event -> event.getFormattedMessage().equals("calling foo error"))
            .findFirst().orElseThrow(AssertionError::new);
}

Log4j2

@Test
public void testLog4J2() {
    //given
    org.apache.logging.log4j.core.Appender appender = mock(org.apache.logging.log4j.core.Appender.class);
    when(appender.getName()).thenReturn("mocked");  //加入appender时需要
    when(appender.isStarted()).thenReturn(true);  //使appender生效时需要
    ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager.getRootLogger() ).addAppender(appender);
    doThrow(new RuntimeException("something wrong...")).when(dependency).getExternalValue(anyString());

    //when
    service.foo();

    //then
    ArgumentCaptor<org.apache.logging.log4j.core.LogEvent> logCaptor = ArgumentCaptor.forClass(org.apache.logging.log4j.core.LogEvent.class);
    verify(appender, atLeast(0)).append(logCaptor.capture());
    logCaptor.getAllValues().stream()
            .filter(event -> event.getMessage().getFormattedMessage().equals("calling foo error"))
            .findFirst().orElseThrow(AssertionError::new);
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,773评论 19 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 47,114评论 6 342
  • 在项目开发过程中,我们可以通过 debug 查找问题。而在线上环境我们查找问题只能通过打印日志的方式查找问题。因此...
    Java架构阅读 8,810评论 2 41
  • 历史 log4j可以当之无愧地说是Java日志框架的元老,1999年发布首个版本,2012年发布最后一个版本,20...
    kelgon阅读 13,427评论 3 53
  • 作为Java开发人员,对于日志记录框架一定非常熟悉。而且几乎在所有应用里面,一定会用到各种各样的日志框架用来记录程...
    意识流丶阅读 14,799评论 0 13