Netty单元测试解决方案(十三)

今天分享 Netty 单元测试解决方案

一种特殊的 Channel 实现—— EmbeddedChannel ,它是 Netty 专门为改进针对ChannelHandler 的单元测试而提供的。将入站数据或者出站数据写入到 EmbeddedChannel 中,然后检查是否有任何东西到达了 ChannelPipeline 的尾端。以这种方式,你便可以确定消息是否已经被编码或者被解码过了,以及是否触发了任何的 ChannelHandler 动作。 writeInbound(Object... msgs) 将入站消息写到 EmbeddedChannel 中。 如果 可以通过 readInbound() 方法从EmbeddedChannel 中读取数据,则返回 true readInbound() 从 EmbeddedChannel 中读取一个入站消息。任何返回的东西都穿越了整个ChannelPipeline。 如果 没有任何可供读取的,则返回 null writeOutbound(Object... msgs) 将出站消息写到 EmbeddedChannel 中。 如果 现在可以通过 readOutbound() 方法从EmbeddedChannel 中读取到什么东西,则返回 true readOutbound() 从 EmbeddedChannel 中读取一个出站消息。任何返回的东西都穿越了整个ChannelPipeline。如果没有任何可供读取的,则返回 null finish() 将 EmbeddedChannel 标记为完成,并且如果有可被读取的入站数据或者出站数据,则返回 true 。这个方法还将会调用 EmbeddedChannel 上的 close() 方法。 入站数据由 ChannelInboundHandler 处理,代表从远程节点读取的数据。出站数据由ChannelOutboundHandler 处理,代表将要写到远程节点的数据。 使用 writeOutbound() 方法将消息写到 Channel 中,并通过 ChannelPipeline 沿着出站的方向传递。随后,你可以使用 readOutbound() 方法来读取已被处理过的消息,以确定结果是否和预期一样。 类似地,对于入站数据,你需要使用 writeInbound() 和 readInbound() 方法。 在每种情况下,消息都将会传递过 ChannelPipeline ,并且被相关的ChannelInboundHandler 或者 ChannelOutboundHandler 处理。   Netty单元测试解决方案(十三)   一、测试入站消息 我们有一个简单的 ByteToMessageDecoder 实现。给定足够的数据,这个实现将产生固定大小的帧。如果没有足够的数据可供读取,它将等待下一个数据块的到来,并将再次检查是否能够产生一个新的帧。这个特定的解码器将产生固定为 3 字节大小的帧。因此,它可能会需要多个事件来提供足够的字节数以产生一个帧。   测试类:
public class FixedLengthFrameDecoderTest {
    @Test
    public void testFramesDecoded() {
        //创建一个 ByteBuf,并存储 9 字节
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);
        }
        ByteBuf input = buf.duplicate();

        EmbeddedChannel channel = new EmbeddedChannel(
                new FixedLengthFrameDecoder(3)
        );

        /*返回false*/
        System.out.println("首次取:"+input.readBytes(1));
        assertFalse(channel.writeInbound(input.readBytes(1)));
        assertFalse(channel.writeInbound(input.readBytes(1)));
        assertTrue(channel.writeInbound(input.readBytes(1)));

        assertTrue(channel.writeInbound(input.readBytes(6)));
        channel.finish();

        // read messages
        //读取所生成的消息,并且验证是否有 3 帧(切片),其中每帧(切片)都为 3 字节
        ByteBuf read = (ByteBuf) channel.readInbound();
        //和源进行比对
        assertEquals(buf.readSlice(3), read);
        read.release();

        read = (ByteBuf) channel.readInbound();
        assertEquals(buf.readSlice(3), read);
        read.release();

        read = (ByteBuf) channel.readInbound();
        assertEquals(buf.readSlice(3), read);
        read.release();

        assertNull(channel.readInbound());
        buf.release();
    }
}

二、测试出站消息

在测试的处理器— AbsIntegerEncoder ,它是 Netty 的 MessageToMessageEncoder 的一个特殊化的实现,用于将负值整数转换为绝对值。 该示例将会按照下列方式工作: 持有 AbsIntegerEncoder 的 EmbeddedChannel 将会以 4 字节的负整数的形式写出站数据; 编码器将从传入的 ByteBuf 中读取每个负整数,并将会调用 Math.abs() 方法来获取其绝对值; 编码器将会把每个负整数的绝对值写到 ChannelPipeline 中。 Netty单元测试解决方案(十三)   测试类:
public class AbsIntegerEncoderTest {
    @Test
    public void testEncoded() {
        //(1) 创建一个 ByteBuf,并且写入 9 个负整数
        ByteBuf buf = Unpooled.buffer();
        for (int i = 1; i < 10; i++) {
            buf.writeInt(i * -1);
        }

        //(2) 创建一个EmbeddedChannel,并安装一个要测试的 AbsIntegerEncoder
        EmbeddedChannel channel = new EmbeddedChannel(
            new AbsIntegerEncoder());
        //(3) 写入 ByteBuf,并断言调用 readOutbound()方法将会产生数据
        assertTrue(channel.writeOutbound(buf));
        //(4) 将该 Channel 标记为已完成状态
        assertTrue(channel.finish());

        // read bytes
        //(5) 读取所产生的消息,并断言它们包含了对应的绝对值
        for (int i = 1; i < 10; i++) {
            int x = channel.readOutbound();
            assertEquals(i, x);
        }
        assertNull(channel.readOutbound());
    }
}

测试结果:

Netty单元测试解决方案(十三)

  三、测试异常处理 应用程序通常需要执行比转换数据更加复杂的任务。例如,你可能需要处理格式不正确的输入或者过量的数据。在下一个示例中,如果所读取的字节数超出了某个特定的限制,我们将会抛出一个 TooLongFrameException 。这是一种经常用来防范资源被耗尽的方法。设定最大的帧大小已经被设置为 3 字节。 如果一个帧的大小超出了该限制,那么程序将会丢弃它的字节,并抛出一个TooLongFrameException。位于 ChannelPipeline 中的其他 ChannelHandler 可以选择在exceptionCaught()方法中处理该异常或者忽略它。   Netty单元测试解决方案(十三) 测试类:
public class FrameChunkDecoderTest {
    @Test
    public void testFramesDecoded() {
        //创建一个 ByteBuf,并向它写入 9 字节
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);
        }
        ByteBuf input = buf.duplicate();

        //创建一个 EmbeddedChannel,并向其安装允许一个帧最大为3字节的
        // FrameChunkDecoder
        EmbeddedChannel channel = new EmbeddedChannel(
            new FrameChunkDecoder(3));

        //向它写入 2 字节,并断言它们将会产生一个新帧
        assertTrue(channel.writeInbound(input.readBytes(2)));
        try {
            //写入一个 4 字节大小的帧,并捕获预期的TooLongFrameException
            channel.writeInbound(input.readBytes(4));
        } catch (TooLongFrameException e) {
            e.printStackTrace();
        }
        //写入剩余的2字节,并断言将会产生一个有效帧
        assertTrue(channel.writeInbound(input.readBytes(3)));
        //将该 Channel 标记为已完成状态
        assertTrue(channel.finish());
    }
}

执行结果:抛出异常,可以调整大小继续测试

Netty单元测试解决方案(十三)

到此,Netty单元测试分享完毕,这些内容就可以在项目中测试使用了,下篇分享Netty 相关的UDP协议,敬请期待!  
上一篇:Netty实战九之单元测试


下一篇:ByteBuf 内部结构设计