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

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

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

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. Обзор встроенного канала

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

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

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

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

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 . Наиболее часто используемые методы:

  • чтение входящих()
  • readOutbound()
  • writeInbound (Объект… сообщения)
  • writeOutbound(Объект… msgs)

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

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

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

channel.writeInbound(httpRequest)

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

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

Давайте рассмотрим простой пример, в котором мы хотим протестировать конвейер, состоящий из двух ChannelHandler , которые получают 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 его генерировать :

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

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

5. Вывод

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

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