MLoggerTest.info仅UAT环境测试失败,其余环境均通过求助
我编写了如下MLoggerTest测试类:
@RunWith(MockitoJUnitRunner.class) public class MLoggerTest { @Mock private Appender<ILoggingEvent> mockAppender; @Captor private ArgumentCaptor<LoggingEvent> captorLoggingEvent; @InjectMocks private MLogger mLogger; private static final String TEST_MESSAGE = "test message"; /** 初始化 */ @Before public void setup() { Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); root.setLevel(Level.DEBUG); root.addAppender(mockAppender); } /** 测试完成后清理 */ @After public void teardown() { Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); root.detachAppender(mockAppender); } @Test public void info() { mLogger.info(TEST_MESSAGE, null); verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture()); final LoggingEvent event = captorLoggingEvent.getValue(); assertThat("Message not logged", event.getFormattedMessage(), containsString(TEST_MESSAGE)); } @Test public void error() { mLogger.error(TEST_MESSAGE, null); verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture()); final LoggingEvent event = captorLoggingEvent.getValue(); assertThat("Message not logged", event.getFormattedMessage(), containsString(TEST_MESSAGE)); } }
运行后出现如下错误:
[ERROR] Failures:
[ERROR] MetricLoggerTest.info:52
Wanted but not invoked:
mockAppender.doAppend();
-> at my.app.logging.MLoggerTest.info(MLoggerTest.java:52)
Actually, there were zero interactions with this mock.
其中info()方法在本地和开发环境均可通过,但在UAT环境失败;error()方法在所有环境都能正常执行,二者逻辑几乎一致,我无法定位问题原因,请求帮助。
更新:触发条件
仅当以下LogExceptionBySalesRefAspectTest测试类先执行时,才会出现上述问题:
@RunWith(MockitoJUnitRunner.class) public class LogExceptionBySalesRefAspectTest { private static final String SALES_REF = "ABC123-87"; private static final String EXCEPTION_MSG = "Exception"; private static final String MESSAGE = "Exception" + EXCEPTION_MSG + "encountered for sales reference " + SALES_REF; private MyAppender myAppender; private static final String LOGGER_NAME = "my.app.logging"; @Before public void setup() { Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME); myAppender = new MyAppender(); myAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); logger.setLevel(Level.ERROR); logger.addAppender(myAppender); myAppender.start(); } @After public void cleanUp() { myAppender.reset(); myAppender.stop(); } @Test public void confirm_output_logged() { LogExceptionBySalesRefAspect aspect = new LogExceptionBySalesRefAspect(); SalesRequest req = SalesRequest.builder().salesReference(QUOTE_REF).build(); JoinPoint jp = Mockito.mock(JoinPoint.class); Mockito.when(jp.getArgs()).thenReturn(new Object[] {req}); aspect.logExceptionWithSalesIdentification(jp, new ServiceException(EXCEPTION_MSG)); assertThat(myAppender.countEventsForLogger(LOGGER_NAME)).isEqualTo(1); assertThat(myAppender.contains(MESSAGE, Level.ERROR)); } @Test public void whenexception_on_postsales_call() { LogExceptionBySalesRefAspect aspect = new LogExceptionBySalesRefAspect(); SalesRequest req = SalesRequest.builder().salesReference(QUOTE_REF).build(); JoinPoint jp = Mockito.mock(JoinPoint.class); Mockito.when(jp.getArgs()).thenReturn(new Object[] {req}); aspect.handlePostSalesExceptionLogging(jp, new ServiceException(EXCEPTION_MSG)); assertThat(myAppender.countEventsForLogger(LOGGER_NAME)).isEqualTo(1); assertThat(myAppender.contains(MESSAGE, Level.ERROR)); } }
核心原因
日志级别全局污染
LogExceptionBySalesRefAspectTest中给my.app.logging这个Logger设置了Level.ERROR,但测试结束后没有恢复原始级别。如果MLogger实际使用的是my.app.logging(或其子Logger,比如my.app.logging.MLogger),那么该Logger的级别会被锁定为ERROR,导致INFO级别的日志被直接过滤,根本不会传递到root Logger的mockAppender,自然触发"零交互"的错误。而ERROR级别日志不受此限制,所以error()测试始终正常。测试隔离不彻底
SLF4J/Logback的Logger上下文是全局共享的,修改Logger配置后必须完全恢复。当前LogExceptionBySalesRefAspectTest的cleanUp方法只处理了自定义Appender的重置和停止,既没恢复Logger的原始级别,也没将Appender从Logger中移除,导致后续测试受到残留配置影响。
修复步骤
1. 修复LogExceptionBySalesRefAspectTest的测试隔离
在该测试类中保存并恢复Logger的原始级别,同时移除自定义Appender:
@RunWith(MockitoJUnitRunner.class) public class LogExceptionBySalesRefAspectTest { // ... 原有常量定义 ... private MyAppender myAppender; private static final String LOGGER_NAME = "my.app.logging"; private Level originalLoggerLevel; // 新增:保存原始级别 @Before public void setup() { Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME); originalLoggerLevel = logger.getLevel(); // 保存Logger原始级别 myAppender = new MyAppender(); myAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); logger.setLevel(Level.ERROR); logger.addAppender(myAppender); myAppender.start(); } @After public void cleanUp() { Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME); logger.setLevel(originalLoggerLevel); // 恢复Logger原始级别 logger.detachAppender(myAppender); // 移除自定义Appender myAppender.reset(); myAppender.stop(); } // ... 原有测试方法 ... }
2. 增强MLoggerTest的稳定性
在MLoggerTest的setup方法中,直接强制设置MLogger对应Logger的级别为DEBUG,避免继承父Logger的错误配置:
@Before public void setup() { Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); root.setLevel(Level.DEBUG); root.addAppender(mockAppender); // 强制设置MLogger使用的Logger级别为DEBUG Logger mLoggerLogger = (Logger) LoggerFactory.getLogger(MLogger.class); mLoggerLogger.setLevel(Level.DEBUG); }
3. 额外验证
确认MLogger的实现代码中,获取Logger的方式是否为LoggerFactory.getLogger(MLogger.class)(或my.app.logging下的子Logger),如果是,那么父Loggermy.app.logging的级别修改就是问题的直接诱因,上述修复可以彻底解决。
内容的提问来源于stack exchange,提问作者runnerpaul

