Перейти к основному содержимому

Утверждение сообщений журнала с помощью JUnit

· 4 мин. чтения

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 .