You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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));
  }
}

问题分析与解决方案

核心原因

  1. 日志级别全局污染
    LogExceptionBySalesRefAspectTest中给my.app.logging这个Logger设置了Level.ERROR,但测试结束后没有恢复原始级别。如果MLogger实际使用的是my.app.logging(或其子Logger,比如my.app.logging.MLogger),那么该Logger的级别会被锁定为ERROR,导致INFO级别的日志被直接过滤,根本不会传递到root Logger的mockAppender,自然触发"零交互"的错误。而ERROR级别日志不受此限制,所以error()测试始终正常。

  2. 测试隔离不彻底
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 13:05:56