一般來(lái)說(shuō),日志是程序相當(dāng)次要的副作用輸出痒留,很少需要專(zhuān)門(mén)的單元測(cè)試來(lái)保證它的行為前硫。不過(guò)也不排除在某些情況下需要在單元測(cè)試中驗(yàn)證日志,比如:
- 某個(gè)場(chǎng)景下日志輸出是下游模塊的關(guān)鍵信息捧书。一般出現(xiàn)在錯(cuò)誤處理分支中。
- 某個(gè)模塊除了日志沒(méi)有顯著的輸出來(lái)檢驗(yàn)它的行為骤星。紅燈经瓷,模塊可測(cè)試性差的表現(xiàn)。
這里介紹以比較常用的logback
和log4j2
為例介紹驗(yàn)證日志的方法洞难,以備不時(shí)之需舆吮。
被測(cè)試代碼
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;
}
}
因?yàn)閮煞N框架外加slf4j中有很多同名的類(lèi),示例代碼中全部帶上完整包名以免混淆。
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);
//通過(guò)ArgumentCaptor捕獲所有l(wèi)og,
//verify默認(rèn)是一次調(diào)用色冀,改為atLeast(0)讓任意次數(shù)記錄log都能驗(yàn)證通過(guò)
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時(shí)需要
when(appender.isStarted()).thenReturn(true); //使appender生效時(shí)需要
((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);
}