如何使用 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()
方法访问实际消息
我们使用 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()
方法访问实际消息