如何轮询Logger避免单元测试SubstituteLogger转Logger类型转换异常
解决SubstituteLogger转logback Logger的轮询实现方案
问题核心
单元测试时日志框架初始化未完成,LoggerFactory.getLogger()会返回临时的SubstituteLogger实例,直接强转ch.qos.logback.classic.Logger就会触发ClassCastException。轮询的思路就是等待真正的logback Logger实例初始化完成后再获取,同时结合ListAppender实现日志捕获。
具体实现步骤
1. 封装轮询获取logback Logger的工具方法
写一个工具方法,设置超时时间和轮询间隔,循环尝试获取Logger,直到拿到真正的logback实例或超时:
import ch.qos.logback.classic.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; public class LogbackLoggerUtils { // 轮询超时时间,可根据实际调整 private static final long TIMEOUT_SECONDS = 5; // 轮询间隔 private static final long POLL_INTERVAL_MS = 100; public static Logger getLogbackLogger(Class<?> clazz) throws InterruptedException { long endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS); org.slf4j.Logger logger = LoggerFactory.getLogger(clazz); while (System.currentTimeMillis() < endTime) { if (logger instanceof Logger) { return (Logger) logger; } // 等待后重新获取Logger,因为LoggerFactory可能已完成初始化 TimeUnit.MILLISECONDS.sleep(POLL_INTERVAL_MS); logger = LoggerFactory.getLogger(clazz); } throw new IllegalStateException("超时未获取到logback Logger实例"); } }
2. 结合ListAppender编写单元测试
在测试中先通过轮询拿到Logger,再绑定ListAppender来捕获日志,测试完成后清理Appender:
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class YourServiceTest { private ListAppender<ILoggingEvent> listAppender; private Logger logbackLogger; private YourService yourService; @BeforeEach void setUp() throws InterruptedException { // 轮询获取logback Logger logbackLogger = LogbackLoggerUtils.getLogbackLogger(YourService.class); // 初始化ListAppender并启动 listAppender = new ListAppender<>(); listAppender.start(); // 将Appender添加到Logger logbackLogger.addAppender(listAppender); yourService = new YourService(); } @AfterEach void tearDown() { // 测试完成后移除Appender,避免干扰其他测试 logbackLogger.detachAppender(listAppender); } @Test void testLogOutput() { // 执行被测方法 yourService.doSomethingThatLogs(); // 验证日志内容 assertEquals(1, listAppender.list.size()); assertTrue(listAppender.list.get(0).getMessage().contains("预期的日志内容")); } }
关键注意点
- 超时时间和轮询间隔可根据项目初始化速度调整,避免过长等待或频繁轮询
- 测试后必须移除
ListAppender,防止多个测试用例之间的日志污染 - 如果是Spring Boot项目,可考虑在
@SpringBootTest初始化完成后再执行轮询,进一步提升稳定性
内容的提问来源于stack exchange,提问作者mattsmith5
相关产品推荐
相关产品推荐

