如何使用 testng 对我的日志消息进行单元测试

How can I unit test my log messages using testng

我们使用 testng 作为测试框架。我们还使用 Lombok @Log4j2 来实例化我们的日志对象。我需要测试一些代码,它在特定条件下记录特定消息。

我看过使用 junit 和 Mockito 的例子。但是我在 testng 中找不到如何做。切换到 junit 不是一种选择。

编辑

我已经实现了一个 class (CaptureLogger),它扩展了 AbstractLogger

import org.apache.logging.log4j.spi.AbstractLogger;

public class CaptureLogger extends AbstractLogger {
    ...
}

我无法将它连接到正在测试的 class 的记录器。

CaptureLogger customLogger = (CaptureLogger) LogManager.getLogger(MyClassUnderTest.class);

生成错误消息:

java.lang.ClassCastException: org.apache.logging.log4j.core.Logger cannot be cast to CaptureLogger

我发现 LogManager.getLogger returns Logger 接口,而不是 Logger 对象(它实现了 Logger 接口)。

如何创建 CaptureLogger 的实例?

只要您使用 Lombok 生成记录器,您就无法使用给定的工具在源代码本身的级别上做太多事情。例如,如果您放置 @Log4j2 注释,它会生成:

private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);

编译后的代码已包含此行。

您可以尝试使用 PowerMockito 模拟 LogManager.getLogger 方法,但我不太喜欢这种工具。不过要说明这一点,因为它可能是一个可行的方向。

有几种方法可以使用框架本身。

一种方法(我对 Log4j2 并不特别熟悉,但它应该提供此功能 - 我多年前对 Log4j 1.x 做了类似的事情)是提供你自己的记录器实现并将其关联在 Log4j2 配置级别使用记录器工厂。 现在如果你这样做,那么 lombok 生成的代码将 return 你的记录器实例,它可以记住在不同级别记录的消息(这是你必须在记录器级别实现的自定义逻辑) .

然后记录器将有一个方法public List<String> getResults(),您将在验证阶段调用以下代码:

   public void test() {
     UnderTest objectUnderTest = ...
     //test test test

     // verification
     MyCustomLogger logger = (MyCutomLogger)LogManager.getLogger(UnderTest.class);

     List<String> results =  logger.getResults();
     assertThat(results, contains("My Log Message with Params I expect or whatever");

   }

我能想到的另一种有点类似的方法是创建一个自定义附加程序,它将记住测试期间发送的所有消息。然后你可以(以声明方式或编程方式将该附加程序绑定到 LogFactory.getLogger 获得的 Logger,用于测试中的 class(或其他 classes,具体取决于你的实际需要)。

然后让测试工作,当涉及到验证时 - 从 log4j2 系统获取对 appender 的引用,并使用一些 public List<String> getResults() 方法请求结果除了方法之外,appender 上必须存在什么它必须实施才能遵守 Appender 合同。

所以测试可能看起来像这样:

public void test () {
     MyTestAppender app = createMemorizingAppender();
     associateAppenderWithLoggerUnderTest(app, UnderTest.class);
     UnderTest underTest = ...

     // do your tests that involve logging operations

     // now the verification phase:

     List<String> results =  app.getResults();
     assertThat(results, contains("My Log Message with Params I expect or whatever");


}

你可以像这样定义你自己的appender:

package com.xyz;

import static java.util.Collections.synchronizedList;

import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;

@Plugin(name = "LogsToListAppender", category = "Core", elementType = Appender.ELEMENT_TYPE)
public class LogsToListAppender extends AbstractAppender {

    private static final List<LogEvent> events = synchronizedList(new ArrayList<>());

    protected LogsToListAppender(String name, Filter filter) {
        super(name, filter, null);
    }

    @PluginFactory
    public static LogsToListAppender createAppender(@PluginAttribute("name") String name,
            @PluginElement("Filter") Filter filter) {
        return new LogsToListAppender(name, filter);
    }

    @Override
    public void append(LogEvent event) {
        events.add(event);
    }

    public static List<LogEvent> getEvents() {
        return events;
    }
}

然后在 class 路径的根目录中创建一个名为 log4j2-logstolist.xml 的文件,其中将引用附加程序:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.xyz" >
    <Appenders>
        <LogsToListAppender name="LogsToListAppender" />
    </Appenders>

    <Loggers>
        <Root level="TRACE">
            <AppenderRef ref="LogsToListAppender" />
        </Root>
    </Loggers>
</Configuration>

您应该特别注意(正确更新)属性 packages="com.xyz"(您的 appender 的包),否则它将不可用。有关详细信息,请查看 https://www.baeldung.com/log4j2-custom-appender

最后创建 TestNG 测试:

package com.xyz;

import static org.testng.Assert.assertTrue;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.config.Configurator;
import org.testng.annotations.Test;

@Test
public class LogsTest {

    static {
        Configurator.initialize(null, "classpath:log4j2-logstolist.xml");
    }

    @Test
    public void testLogs() {
        // call your code that produces log, e.g.
        LogManager.getLogger(LogsTest.class).trace("Hello");
        assertTrue(LogsToListAppender.getEvents().size() > 0);
    }
}

如您所见,我们在 class 初始化(static{} 块)时强制 Log4j2 使用带有 Configurator.initialize(null, "classpath:log4j2-logstolist.xml"); 的自定义配置。

请记住,检查记录器名称对您也很有用,例如LogsToListAppender.getEvents().stream().filter(a -> CLASS_THAT_PRODUCES_LOG.class.getName().equals(a.getLoggerName())).collect(toList());

您可以使用 LogEvent::getMessage() 方法访问实际消息