• Netty实战九之单元测试


    ChannelHandler是Netty应用程序的关键元素,所以彻底地测试他们应该是你的开发过程的一个标准部分。最佳实践要求你的测试不仅要能够证明你的实现是正确的,而且还要能够很容易地隔离那些因修改代码而突然出现的问题。这种类型的测试叫做单元测试。

    其基本思想是,以尽可能小的区块测试你的代码,并且尽可能地和其他的代码模块以及运行时的依赖相隔离。

    1、EmbeddedChannel概述

    你已经知道,可以将ChannelPipeline中的ChannelHandler实现连接在一起,以构建你的应用程序的业务逻辑。之前已经解释过,这种设计支持将任何潜在的复杂处理过程分解为小的可重用的组件,每个组件都将处理一个明确定义的任务或者步骤。

    Netty提供了它所谓的Embedded传输,用于测试ChannelHandler。这个传输是一种特殊的Channel实现——EmbeddedChannel——的功能。这是实现提供了通过ChannelPipeline传播事件的简便方法。

    这个想法是直截了当的:将入站数据或者出站数据写入到EmbeddedChannel中,然后检查是否有任何东西到达了ChannelPipeline的尾端。以这种方式,你便可以确定消息是否被编码或者被解码过了,以及是否触发了任何的ChannelHandler动作。

    下图展示了使用EmbeddedChannel的方法,数据是如何流经ChannelPipeline的。你可以使用writeOutbound()方法将消息写到Channel中,并通过ChannelPipeline沿着出站的方向传递。随后,你可以使用readOutbound()方法来读取已被处理过的消息,已确定结果是否和预期一样。类似地,对于入站数据,你需要使用writeInbound()和readInbound()方法。

    在每种情况下,消息都将会传递过ChannelPipeline,并且被相关的ChannelInboundHandler或者ChannelOutboundHandler处理。如果消息没有被消费,那么你可以使用readInbound()或者readOutbound()方法来在处理过了这些消息之后,酌情把它们从Channel中读出来。 输入图片说明

    2、使用EmbeddedChannel测试ChannelHandler

    JUnit断言

    org.junit.Assert 类提供了很多用于测试的静态方法。失败的断言将导致一个异常被抛出,并将终止当前正在执行中的测试。导入这些断言的最高效的方式是通过一个import static语句来实现:

    import static org.junit.Assert.*;

    一旦这样做了,就可以直接调用Assert方法了:

    assertEquals(buf.readSlice(3),read);

    3、测试入站消息

    下图展示了一个简单的ByteToMessageDecoder实现。给定足够的数据,这个实现将产生固定大小的帧。如果没有足够的数据可供读取,它将等待下一个数据块的到来,并将再次检查是否产生一个新的帧。 输入图片说明可以从图中右侧的帧看到的那样,这个特定的解码器将产生固定为3字节大小的帧。因此,它可能会需要多个事件来提供足够的字节数以产生一个帧。

    最终,每个帧都会被传递给ChannelPipeline中的下一个ChannelHandler,该解码器的实现如下代码所示。

    //扩展ByteToMessageDecoder以处理入站字节,并将它们解码为消息
    public class FixedLengthFrameDecoder extends ByteToMessageDecoder{
    
        private final int frameLength;
    
        //指定要生成的帧的长度
        public FixedLengthFrameDecoder(int frameLength) throws IllegalAccessException {
            if (frameLength <= 0){
                throw new IllegalAccessException("frameLength must be a positive integer: " + frameLength);
            }
            this.frameLength = frameLength;
        }
    
        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf,
                              List<Object> list) throws Exception {
            //检查是否有足够的字节可以被读取,以生成下一个帧
            while (byteBuf.readableBytes() >= frameLength){
                //从ByteBuf中读取一个新帧
                ByteBuf buf = byteBuf.readBytes(frameLength);
                //将该帧添加到已被解码的消息列表中
                list.add(buf);
            }
        }
    }
    
    

    以下代码展示了一个使用EmbeddedChannel的对于前面代码的测试

    public class FixedLengthFrameDecoderTest {
        @Test
        public void decode() throws Exception {
            //创建一个ByteBuf,并存储9个字节
            ByteBuf buf = Unpooled.buffer();
            for (int i = 0; i < 9; i++){
                buf.writeByte(i);
            }
    
            ByteBuf input = buf.duplicate();
            //创建一个EmbeddedChannel,并添加一个FixedLengthFrameDecoder,其将以3字节的帧长度被测试
            EmbeddedChannel channel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
            //write bytes
            //将数据写入EmbeddedChannel
            assertTrue(channel.writeInbound(input.retain()));
            //标记Channel为已完成状态
            assertTrue(channel.finish());
    
            //read messages
            //读取所生成的消息,并且验证是否有3帧,其中每帧都为3字节
            ByteBuf read = (ByteBuf)channel.readInbound();
            assertEquals(buf.readSlice(3),read);
            read.release();
    
            assertNull(channel.readInbound());
            buf.release();
        }
    
    }
    

    4、测试出站消息

    简单地提及我们正在测试的处理器——AbsIntegerEncoder,它是netty的MessageToMessageEncoder的一个特殊化的实现,用于将负值整数转换为绝对值。

    该示例将会按照下列方式工作:

    ——持有AbsIntegerEncoder的EmbeddedChannel将会以4字节的负整数的形式写出站数据。

    ——编码器将从传入的ByteBuf中读取每个负整数,并将会调用Math.abs()方法来获取其绝对值

    ——编码器将会把每个负整数的绝对值写到ChannelPipeline中。 输入图片说明以下代码实现了这个逻辑。

    public class AbsIntegerEncoder extends MessageToMessageEncoder<ByteBuf>{
    
        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf,
                              List<Object> list) throws Exception {
            //检查是否有足够的字节用来编码
            while (byteBuf.readableBytes() >= 4){
                //从输入的ByteBuf中读取下一个整数,并且计算其绝对值
                int value = Math.abs(byteBuf.readInt());
                //将该整数写入到编码消息的List中
                list.add(value);
            }
        }
    }
    
    

    以下代码使用了EmbeddedChannel来测试代码

    public class AbsIntegerEncoderTest {
        @Test
        public void encode() throws Exception {
            //创建一个ByteBuf,并且写入9个负整数
            ByteBuf buf = Unpooled.buffer();
            for (int i = 1; i < 10; i++){
                buf.writeInt(i * -1);
            }
            //创建一个EmbeddedChannel,并安装一个要测试的AbsIntegerEncoder
            EmbeddedChannel channel = new EmbeddedChannel(
                    new AbsIntegerEncoder());
            //写入ByteBuf,并断言调用readOutbound()方法将会产生数据
            assertTrue(channel.writeOutbound(buf));
            assertTrue(channel.finish());
    
            //读取所产生的消息,并断言它们包含了对应的绝对值
            //read bytes
            for (int i=1; i < 10; i++){
                assertEquals(i , channel.readOutbound());
            }
            assertNull(channel.readOutbound());
        }
    
    }
    

    5、测试异常处理

    应用程序通常需要执行比转换数据更加复杂的任务。例如,你可能需要处理格式不正确的输入或者过量的数据。下一个示例中,如果所读取的字节数超出了特定的限制,我们将会抛出一个TooLongFrameException。这是一种经常用来防范资源被耗尽的方法。

    如下图,最大的帧大小已经被设置为3字节,如果一个帧的大小超过了该限制,那么程序将会丢弃它的字节,并抛出一个TooLongFrameException。位于ChannelPipeline中的其它ChannelHandler可以选择在exceptionCaught()方法中处理该异常或者忽略它。 输入图片说明其实现如下代码所示。

    public class FrameChunkDecoder extends ByteToMessageDecoder{
    
        private final int maxFrameSize;
    
        public FrameChunkDecoder(int maxFrameSize) {
            this.maxFrameSize = maxFrameSize;
        }
    
        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext,
                              ByteBuf in, List<Object> out) throws Exception {
            int readableBytes = in.readableBytes();
            //如果该帧太大,则丢弃它并抛出异常
            if (readableBytes > maxFrameSize){
                //discard the bytes
                in.clear();
                throw new TooLongFrameException();
            }
            //从ByteBuf中读取一个新的帧
            ByteBuf buf = in.readBytes(readableBytes);
            //将该帧添加到解码消息的List中
            out.add(buf);
        }
    }
    

    使用的Try/Catch块是EmbeddedChannel的一个特殊功能。如果其中一个write*方法产生了一个受检查的Exception,那么它将会被包装在一个RuntimeException中并抛出,这使得可以容易地测试出一个Exception是否在处理数据的过程中已经被处理了。

    交流群:628793702
  • 相关阅读:
    第一周软件测试
    第八周
    第七
    安卓第六周作业
    安卓第五周
    第四周作业
    Jsp第十次作业
    Jsp第九次作业(带验证码的email)
    Jsp第八次作业
    Jsp第七次作业email
  • 原文地址:https://www.cnblogs.com/UncleCatMySelf/p/9190628.html
Copyright © 2020-2023  润新知