Мы используем testng в качестве фреймворка для тестирования. Мы также используем Lombok @Log4j2 для создания экземпляров наших объектов журнала. Мне нужно протестировать некоторый код, который регистрирует определенные сообщения при определенных условиях.
Я видел примеры использования junit и Mockito. Но я не могу найти, как это сделать в testng. Переход на junit не вариант.
Я реализовал класс (CaptureLogger), который расширяет AbstractLogger.
import org.apache.logging.log4j.spi.AbstractLogger;
public class CaptureLogger extends AbstractLogger {
...
}
Я не могу подключить его к регистратору для тестируемого класса.
CaptureLogger customLogger = (CaptureLogger) LogManager.getLogger(MyClassUnderTest.class);
выдает сообщение об ошибке:
java.lang.ClassCastException: org.apache.logging.log4j.core.Logger cannot be cast to CaptureLogger
Я обнаружил, что LogManager.getLogger возвращает интерфейс Logger, а не объект Logger (который реализует интерфейс Logger).
Как мне создать экземпляр моего CaptureLogger?
Пока вы используете Lombok для создания регистратора, вы мало что можете сделать на уровне самого исходного кода с помощью данных инструментов. Например, если вы разместите аннотацию @Log4j2
, она сгенерирует:
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
Скомпилированный код уже содержит эту строку.
Вы можете попробовать издеваться над методом LogManager.getLogger с помощью PowerMockito, но мне не очень нравятся такие инструменты. Утверждая это, хотя, поскольку это может быть жизнеспособным направлением.
Есть несколько способов работы с самим фреймворком.
Один из способов (и я не знаком конкретно с Log4j2, но он должен предлагать эту возможность - я делал что-то подобное с Log4j 1.x много лет назад) - предоставить собственную реализацию регистратора и связать ее с фабрикой регистратора на уровне конфигураций Log4j2. Теперь, если вы сделаете это, то код, сгенерированный lombok, вернет ваш экземпляр регистратора, который может запоминать сообщения, зарегистрированные на разных уровнях (это пользовательская логика, которую вам придется реализовать на уровне регистратора).
Тогда у регистратора будет метод 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");
}
Еще один похожий способ, который я могу придумать, - это создать настраиваемое приложение, которое будет запоминать все сообщения, отправленные во время теста. Затем вы можете (декларативно или программно привязать этот аппендикс к Logger, полученному LogFactory.getLogger
для тестируемого класса (или также для других классов в зависимости от ваших реальных потребностей).
Затем пусть тест работает, а когда дело доходит до проверки - получайте ссылку на аппендер из системы log4j2 и запрашивайте результаты каким-то public List<String> getResults()
методом, который должен существовать на аппендере в дополнение к методам, которые он должен реализовывать, чтобы подчиняться Дополнительный договор.
Таким образом, тест может выглядеть примерно так:
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");
}
Я думаю (и, как я уже сказал, я никогда не делал этого с log4j2.x), вам нужно настроить сам log4j2. Например, см. здесь: logging.apache.org/log4j/2.x/manual/extending.html
Вы можете определить свой собственный аппендер следующим образом:
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;
}
}
Затем создайте файл с именем 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"
(пакет вашего приложения), иначе он будет недоступен. Для получения дополнительной информации проверьте 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);
}
}
Как видите, мы заставляем Log4j2 использовать пользовательскую конфигурацию с Configurator.initialize(null, "classpath:log4j2-logstolist.xml");
при инициализации класса (блок static{}
).
Имейте в виду, что вам также будет полезно проверить имя регистратора, например. LogsToListAppender.getEvents().stream().filter(a -> CLASS_THAT_PRODUCES_LOG.class.getName().equals(a.getLoggerName())).collect(toList());
вы можете получить доступ к фактическому сообщению, используя метод LogEvent::getMessage()
Я пробовал это (см. мое редактирование), но это не совсем работает.