«1. Введение

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

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

К счастью, платформа предоставляет нам класс EmbeddedChannel, который облегчает тестирование ChannelHandlers.

2. Настройка

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

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

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.24.Final</version>
</dependency>

3. EmbeddedChannelOverview

Класс EmbeddedChannel — это просто еще одна реализация AbstractChannel, которая передает данные без необходимости реального сетевого подключения.

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

Чтобы протестировать один или несколько ChannelHandler, нам сначала нужно создать экземпляр EmbeddedChannel, используя один из его конструкторов.

Наиболее распространенным способом инициализации EmbeddedChannel является передача списка обработчиков каналов его конструктору:

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

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

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

Кроме того, когда мы создаем EmbeddedChannel, он будет иметь конфигурацию по умолчанию, заданную классом DefaultChannelConfig.

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

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

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

    readInbound() readOutbound() writeInbound(Object… msgs) writeOutbound(Object… msgs)

Методы чтения извлекают и удаляют первый элемент входящей/исходящей очереди. нам нужен доступ ко всей очереди сообщений без удаления какого-либо элемента, мы можем использовать метод outboundMessages():

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

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

channel.writeInbound(httpRequest)

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

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

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

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

~ ~~ Первый, HttpMessageHandler, извлечет данные из 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 — это сообщение, полученное в результате отправки данных после их обработки всеми обработчиками ChannelHandler во входящем конвейере.

Теперь давайте проверим, отвечает ли наш сервер 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(), но в некоторых случаях мы не хотим обрабатывать исключение и вместо этого передаем его следующему ChannelHandler в конвейере.

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

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

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

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

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

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

«