Тестирование Netty с EmbeddedChannel

Тестирование Netty с EmbeddedChannel

1. Вступление

В этой статье мы увидим, как использоватьEmbeddedChannel to для проверки функциональности наших обработчиков входящего и исходящего каналов.

Netty - очень универсальная среда для написания высокопроизводительных асинхронных приложений. Модульное тестирование таких приложений может быть сложно без правильных инструментов.

К счастью, фреймворк предоставляет намEmbeddedChannel class – which facilitates the testing of ChannelHandlers.

2. Настроить

EmbeddedChannel является частью инфраструктуры Netty, поэтому единственная необходимая зависимость - это зависимость для самой Netty.

Зависимость можно найти вMaven Central:


    io.netty
    netty-all
    4.1.24.Final

3. EmbeddedChannelOverviewс

EmbeddedChannelclass is just another implementation of AbstractChannel –, который передает данныеwithout the need for a real network connection.

Это полезно, потому что мы можем моделировать входящие сообщения, записывая данные по входящим каналам, а также проверять сгенерированный ответ на исходящих каналах. Таким образом, мы можем индивидуально протестировать каждыйChannelHandler сор во всем конвейере канала.

Чтобы протестировать один или несколькоChannelHandlers, we сначала должен создать синстанциюEmbeddedChannel , используя один из своих конструкторов.

Самый распространенный способ инициализироватьEmbeddedChannel is путем передачи спискаChannelHandlers to его конструктору:

EmbeddedChannel channel = new EmbeddedChannel(
  new HttpMessageHandler(), new CalculatorOperationHandler());

Если мы хотим иметь больший контроль над порядком вставки обработчиков в конвейер, мы можем создатьEmbeddedChannel с конструктором по умолчанию и напрямую добавить обработчики:

channel.pipeline()
  .addFirst(new HttpMessageHandler())
  .addLast(new CalculatorOperationHandler());

Такжеwhen we create an EmbeddedChannel, it’ll have a default configuration given by the DefaultChannelConfigclass.

Когда мы хотим использовать настраиваемую конфигурацию, например, уменьшить значение тайм-аута подключения по сравнению с значением по умолчанию, мы можем получить доступ к объектуChannelConfig object, используя методconfig():

DefaultChannelConfig channelConfig = (DefaultChannelConfig) channel
  .config();
channelConfig.setConnectTimeoutMillis(500);

EmbeddedChannel включает методы, которые мы можем использовать для чтения и записи данных в нашChannelPipeline. Наиболее часто используемые методы:

  • readInbound ()

  • readOutbound ()

  • writeInbound (Объект… сообщения)

  • writeOutbound (Объект… сообщения)

The read methods retrieve and remove the first element in the inbound/outbound queue.Если нам нужен доступ ко всей очереди сообщений без удаления какого-либо элемента, мы можем использовать методoutboundMessages() :

Object lastOutboundMessage = channel.readOutbound();
Queue allOutboundMessages = channel.outboundMessages();


Методы записи возвращаютtrue w, если сообщение было успешно добавлено во входящий / исходящий конвейерChannel:.

channel.writeInbound(httpRequest)

Идея состоит в том, что мы пишем сообщения во входящий конвейер, чтобы outChannelHandlerswill их обрабатывал, и мы ожидаем, что результат будет доступен для чтения из исходящего конвейера.

4. ТестированиеChannelHandlers

Давайте рассмотрим простой пример, в котором мы хотим протестировать конвейер, состоящий из двухChannelHandlers , которые получают HTTP-запрос и ожидают ответа HTTP, содержащего результат вычисления:

EmbeddedChannel channel = new EmbeddedChannel(
  new HttpMessageHandler(), new CalculatorOperationHandler());

Первый,HttpMessageHandler w, извлекает данные из HTTP-запроса и передает их секундамChannelHandler через конвейер,CalculatorOperationHandler, для обработки данных.

Теперь давайте напишем HTTP-запрос и посмотрим, обрабатывает ли его входящий конвейер:

FullHttpRequest httpRequest = new DefaultFullHttpRequest(
  HttpVersion.HTTP_1_1, HttpMethod.GET, "/calculate?a=10&b=5");
httpRequest.headers().add("Operator", "Add");

assertThat(channel.writeInbound(httpRequest)).isTrue();
long inboundChannelResponse = channel.readInbound();
assertThat(inboundChannelResponse).isEqualTo(15);

Мы видим, что мы отправили HTTP-запрос во входящий конвейер с помощью методаwriteInbound()  и прочитали результат с помощьюreadInbound(); inboundChannelResponse  - это сообщение, которое явилось результатом отправленных нами данных после их обработки всемиChannelHandlers входящим конвейером.

Теперь давайте проверим, отвечает ли наш Netty-сервер правильным ответным сообщением HTTP. Для этого мы проверим, существует ли сообщение в исходящем конвейере:

assertThat(channel.outboundMessages().size()).isEqualTo(1);

Исходящее сообщение в данном случае является HTTP-ответом, поэтому давайте проверим правильность содержания. Мы делаем это, читая последнее сообщение в исходящем конвейере:

FullHttpResponse httpResponse = channel.readOutbound();
String httpResponseContent = httpResponse.content()
  .toString(Charset.defaultCharset());
assertThat(httpResponseContent).isEqualTo("15");

4. Тестирование обработки исключений

Другой распространенный сценарий тестирования - обработка исключений.

Мы можем обрабатывать исключения в нашемChannelInboundHandlers , реализуя методexceptionCaught() , но есть некоторые случаи, когда мы не хотим обрабатывать исключение, и вместо этого мы передаем его следующемуChannelHandlerin трубопровод.

Мы можем использовать методcheckException()  из классаEmbeddedChannel, чтобы проверить, был ли получен какой-либо объектThrowable object по конвейеру, и повторно его выбросить.

Таким образом, мы можем пойматьException and check, должен или не должен былChannelHandlerбросать его:

assertThatThrownBy(() -> {
    channel.pipeline().fireChannelRead(wrongHttpRequest);
    channel.checkException();
}).isInstanceOf(UnsupportedOperationException.class)
  .hasMessage("HTTP method not supported");

В приведенном выше примере мы видим, что отправили HTTP-запрос, который, как мы ожидаем, вызоветException. Используя методcheckException() , мы можем повторно вызвать последнее исключение, которое существует в конвейере, чтобы мы могли утверждать, что от него требуется.

5. Заключение

EmbeddedChannel  - отличная функция, предоставляемая фреймворком Netty, чтобы помочь нам проверить правильность spipeline outChannelHandler . Его можно использовать для тестирования каждогоChannelHandler по отдельности и, что более важно, всего конвейера.

Исходный код статьи доступенover on GitHub.