1. Введение
В этом руководстве мы рассмотрим, как мы можем охватить сгенерированные журналы в тестировании JUnit .
Мы будем использовать slf4j-api
и реализацию logback,
а также создадим пользовательский присоединитель, который мы сможем использовать для утверждения журнала . ** **
2. Зависимости Maven
Прежде чем мы начнем, давайте добавим зависимость logback
. Поскольку он изначально реализует slf4j-api
, он автоматически загружается и внедряется в проект транзитивностью Maven:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>.
<version>1.2.6</version>
</dependency>
AssertJ
предлагает очень полезные функции при тестировании, поэтому давайте также добавим его зависимость в проект:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
3. Базовая бизнес-функция
Теперь давайте создадим объект, который будет генерировать журналы, на которых мы сможем основывать наши тесты.
Наш объект BusinessWorker
будет предоставлять только один метод. Этот метод создаст журнал с одинаковым содержимым для каждого уровня журнала. Хотя этот метод не так полезен в реальном мире, он хорошо подойдет для наших целей тестирования:
public class BusinessWorker {
private static Logger LOGGER = LoggerFactory.getLogger(BusinessWorker.class);
public void generateLogs(String msg) {
LOGGER.trace(msg);
LOGGER.debug(msg);
LOGGER.info(msg);
LOGGER.warn(msg);
LOGGER.error(msg);
}
}
4. Тестирование журналов
Мы хотим генерировать журналы, поэтому давайте создадим файл logback.xml в папке
src/test/resources
. Давайте сделаем это как можно проще и перенаправим все журналы в приложение CONSOLE
:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<root level="error">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
4.1. ПамятьAppender
Теперь давайте создадим собственный аппендер, который хранит журналы в памяти . Мы расширим ListAppender<ILoggingEvent>
, который предлагает logback
, и дополним его несколькими полезными методами:
public class MemoryAppender extends ListAppender<ILoggingEvent> {
public void reset() {
this.list.clear();
}
public boolean contains(String string, Level level) {
return this.list.stream()
.anyMatch(event -> event.toString().contains(string)
&& event.getLevel().equals(level));
}
public int countEventsForLogger(String loggerName) {
return (int) this.list.stream()
.filter(event -> event.getLoggerName().contains(loggerName))
.count();
}
public List<ILoggingEvent> search(String string) {
return this.list.stream()
.filter(event -> event.toString().contains(string))
.collect(Collectors.toList());
}
public List<ILoggingEvent> search(String string, Level level) {
return this.list.stream()
.filter(event -> event.toString().contains(string)
&& event.getLevel().equals(level))
.collect(Collectors.toList());
}
public int getSize() {
return this.list.size();
}
public List<ILoggingEvent> getLoggedEvents() {
return Collections.unmodifiableList(this.list);
}
}
Класс MemoryAppender
обрабатывает список
, который автоматически заполняется системой ведения журнала.
Он предоставляет множество методов для охвата широкого круга целей тестирования:
reset()
— очищает списокcontains(msg, level)
— возвращаетtrue
, только если список содержит событиеILoggingEvent
, соответствующее указанному содержимому и уровню важности.countEventForLoggers(loggerName)
— возвращает количествособытий ILoggingEvent
, сгенерированных именованным регистратором.search(msg)
— возвращаетсписок
событийILoggingEvent,
соответствующих определенному содержимому.search(msg, level)
— возвращаетсписок
событийILoggingEvent,
соответствующих указанному содержимому и уровню важности.getSize()
— возвращает количествособытий ILoggingEvent
.getLoggedEvents()
— возвращает неизменяемый вид элементовILoggingEvent.
4.2. Модульный тест
Далее давайте создадим тест JUnit для нашего бизнес-воркера.
Мы объявим наш MemoryAppender
как поле и программно внедрим его в систему журналов. Затем мы начнем приложение.
Для наших тестов мы установим уровень DEBUG
:
@Before
public void setup() {
Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME);
memoryAppender = new MemoryAppender();
memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
logger.setLevel(Level.DEBUG);
logger.addAppender(memoryAppender);
memoryAppender.start();
}
Теперь мы можем создать простой тест, в котором мы создаем экземпляр класса BusinessWorker и вызываем метод
generateLogs
. Затем мы можем делать утверждения в журналах, которые он генерирует:
@Test
public void test() {
BusinessWorker worker = new BusinessWorker();
worker.generateLogs(MSG);
assertThat(memoryAppender.countEventsForLogger(LOGGER_NAME)).isEqualTo(4);
assertThat(memoryAppender.search(MSG, Level.INFO).size()).isEqualTo(1);
assertThat(memoryAppender.contains(MSG, Level.TRACE)).isFalse();
}
В этом тесте используются три функции MemoryAppender
:
- Было сгенерировано четыре журнала — должна присутствовать одна запись для каждой серьезности с отфильтрованным уровнем трассировки.
- Только одна запись в журнале с
сообщением
о содержимом с уровнем серьезностиINFO
- Нет записи в журнале с
сообщением о
содержимом и серьезностьюTRACE
Если мы планируем использовать один и тот же экземпляр этого класса внутри одного и того же тестового класса при создании большого количества журналов, использование памяти возрастет. Мы можем вызывать метод MemoryAppender.clear()
перед каждым тестом, чтобы освободить память и избежать OutOfMemoryException
.
В этом примере мы сократили объем сохраняемых журналов до пакета LOGGER_NAME
, который мы определили как « com.foreach.junit.log
». Потенциально мы могли бы сохранить все журналы с помощью LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME),
но мы должны избегать этого, когда это возможно, так как это может потреблять много памяти .
5. Вывод
В этом руководстве мы продемонстрировали, как создать журнал в наших модульных тестах .
Как всегда, код можно найти на GitHub .