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 处理。
 
 
一、测试入站消息
我们有一个简单的 ByteToMessageDecoder 实现。给定足够的数据,这个实现将产生固定大小的帧。如果没有足够的数据可供读取,它将等待下一个数据块的到来,并将再次检查是否能够产生一个新的帧。这个特定的解码器将产生固定为 3 字节大小的帧。因此,它可能会需要多个事件来提供足够的字节数以产生一个帧。
业务类:
/**
 *  FixedLengthFrameDecoder
 */
//扩展 ByteToMessageDecoder 以处理入站字节,并将它们解码为消息
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
    private final int frameLength;

    //指定要生成的帧的长度
    public FixedLengthFrameDecoder(int frameLength) {
        if (frameLength <= 0) {
            throw new IllegalArgumentException(
                "frameLength must be a positive integer: " + frameLength);
        }
        this.frameLength = frameLength;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in,
                          List<Object> out) throws Exception {
        //检查是否有足够的字节可以被读取,以生成下一个帧
        while (in.readableBytes() >= frameLength) {
            //从 ByteBuf 中读取一个新帧
            ByteBuf buf = in.readBytes(frameLength);
            //将该帧添加到已被解码的消息列表中
            out.add(buf);
        }
    }
}

测试类:

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 中。
业务类:
//扩展 MessageToMessageEncoder 以将一个消息编码为另外一种格式
public class AbsIntegerEncoder extends
        MessageToMessageEncoder<ByteBuf> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,
                          ByteBuf in, List<Object> out) throws Exception {
        //检查是否有足够的字节用来编码,int为4个字节
        while (in.readableBytes() >= 4) {
            //从输入的 ByteBuf中读取下一个整数,并且计算其绝对值
            int value = Math.abs(in.readInt());
            //将该整数写入到编码消息的 List 中
            out.add(value);
        }
    }
}

测试类:

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());
    }
}

测试结果:

 
三、测试异常处理
应用程序通常需要执行比转换数据更加复杂的任务。例如,你可能需要处理格式不正确的输入或者过量的数据。在下一个示例中,如果所读取的字节数超出了某个特定的限制,我们将会抛出一个 TooLongFrameException 。这是一种经常用来防范资源被耗尽的方法。设定最大的帧大小已经被设置为 3 字节。 如果一个帧的大小超出了该限制,那么程序将会丢弃它的字节,并抛出一个TooLongFrameException。位于 ChannelPipeline 中的其他 ChannelHandler 可以选择在exceptionCaught()方法中处理该异常或者忽略它。
 
业务类:
/**
 *  FrameChunkDecoder
 */
//扩展 ByteToMessageDecoder以将入站字节解码为消息
public class FrameChunkDecoder extends ByteToMessageDecoder {
    private final int maxFrameSize;

    //指定将要产生的帧的最大允许大小
    public FrameChunkDecoder(int maxFrameSize) {
        this.maxFrameSize = maxFrameSize;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in,
                          List<Object> out)
        throws Exception {
        int readableBytes = in.readableBytes();
        if (readableBytes > maxFrameSize) {
            //如果该帧超出允许的大小,则丢弃它并抛出一个 TooLongFrameException……
            in.clear();
            throw new TooLongFrameException();
        }
        //……否则,从 ByteBuf 中读取一个新的帧
        ByteBuf buf = in.readBytes(readableBytes);
        //将该帧添加到解码 读取一个新的帧消息的 List 中
        out.add(buf);
    }
}

测试类:

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 相关的UDP协议,敬请期待!
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值