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,该解码器的实现如下代码所示。

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

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

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

4、测试出站消息

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

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

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

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

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

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

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

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

5、测试异常处理

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

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

  1. public class FrameChunkDecoder extends ByteToMessageDecoder{
  2. private final int maxFrameSize;
  3. public FrameChunkDecoder(int maxFrameSize) {
  4. this.maxFrameSize = maxFrameSize;
  5. }
  6. @Override
  7. protected void decode(ChannelHandlerContext channelHandlerContext,
  8. ByteBuf in, List<Object> out) throws Exception {
  9. int readableBytes = in.readableBytes();
  10. //如果该帧太大,则丢弃它并抛出异常
  11. if (readableBytes > maxFrameSize){
  12. //discard the bytes
  13. in.clear();
  14. throw new TooLongFrameException();
  15. }
  16. //从ByteBuf中读取一个新的帧
  17. ByteBuf buf = in.readBytes(readableBytes);
  18. //将该帧添加到解码消息的List中
  19. out.add(buf);
  20. }
  21. }

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

Netty实战九之单元测试的更多相关文章

  1. Netty 系列五(单元测试).

    一.概述和原理 Netty 的单元测试,主要是对业务逻辑的 ChannelHandler 做测试(毕竟对 Bootstrap.EventLoop 这些做测试着实没有多大意义),模拟一次入站数据或者出站 ...

  2. 重磅!阿里P8费心整理Netty实战+指南+项目白皮书PDF,总计1.08G

    前言 Netty是一款用于快速开发高性能的网络应用程序的Java框架.它封装了网络编程的复杂性,使网络编程和Web技术的最新进展能够被比以往更广泛的开发人员接触到. Netty不只是一个接口和类的集合 ...

  3. 《Netty实战》源码运行及本地环境搭建

     1.源码路径: GitHub - zzzvvvxxxd/netty-in-action-cn: Netty In Action 中文版 ,中文唯一正版<Netty实战>的代码清单 下载后 ...

  4. 1.Netty 实战前言

    1.参考文档:Netty实战精髓篇 2.Netty介绍:     Netty是基于Java NIO的网络应用框架. Netty是一个NIO client-server(客户端服务器)框架,使用Nett ...

  5. SpringSecurity权限管理系统实战—九、数据权限的配置

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  6. 1、Netty 实战入门详解

    一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...

  7. Netty实战十四之案例研究(一)

    1.Droplr——构建移动服务 Bruno de Carvalho,首席架构师 在Droplr,我们在我的基础设施的核心部分.从我们的API服务器到辅助服务的各个部分都使用了Netty. 这是一个关 ...

  8. Netty实战四之传输

    流经网络的数据总是具有相同的类型:字节(网络传输——一个帮助我们抽象底层数据传输机制的概念) Netty为它所有的传输实现提供了一个通用的API,即我们可以将时间花在其他更有成效的事情上. 我们将通过 ...

  9. Netty实战入门详解——让你彻底记住什么是Netty(看不懂你来找我)

    一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...

随机推荐

  1. javascript中数组总结

    数组是所有高级语言都会有的东西,数组是JS中使用最多的类型之一,所以掌握JS中数组的用法相当有帮助: 由于JS是一门弱类型的语言,所以数组里面可以放各种不同的数据类型,比如 var a = [1993 ...

  2. 包建强的培训课程(14):Android与ReactNative

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  3. 全国计算机等级考试二级Python语言程序设计考试大纲

    全国计算机等级考试二级Python语言程序设计考试大纲(2018年版) 基本要求 掌握Python语言的基本语法规则. 掌握不少于2个基本的Python标准库. 掌握不少于2个Python第三方库,掌 ...

  4. Hadoop 电话通信清单

    一.实例要求 现有一批电话通信清单,记录了用户A拨打某些特殊号码(如120,10086,13800138000等)的记录.需要做一个统计结果,记录拨打给用户B的所有用户A. 二.测试样例 样例输入: ...

  5. Android NDK学习(五):Java调用Native代码流程总结

    编写一个Java类,并且在某个方法签名的修饰符中加上native修饰符. 使用Javac命令编译第一步中的Java类,使之成为一个class文件. 使用Javah -jni 包名.类名 生成Jni接口 ...

  6. Java程序员的成长之路

    阅读本文大概需要 8.2 分钟. tips:虽然题目是写的Java程序员,但对其他语言的开发来说也会有借鉴作用. 本篇介绍的是大体思路,以及每个节点所需要学习的书籍内容,如果大家对详细的技术点有需要, ...

  7. electron-builder 由于网络原因无法下载问题解决

    electron-builder 由于网络原因无法下载问题解决 在package.json的build中添加electron的镜像 "electronDownload": { &q ...

  8. centos7进入单用户模式

    当我们设置用户密码时,有可能会忘记,这时如何登陆呢,单用户模式就可以 首先我们进入开机界面,按e进行选择 会进入以下界面, 然后找到图中红线标注的该行,在行尾添加 init=/bin/sh 按住Ctr ...

  9. 【ABP框架系列学习】介绍篇(1)

      0.引言 该系列博文主要在[官方文档]及[tkbSimplest]ABP框架理论研究系列博文的基础上进行总结的,或许大家会质问,别人都已经翻译过了,这不是多此一举吗?原因如下: 1.[tkbSim ...

  10. CAS单点登陆/oAuth2授权登陆

    单点登陆 CAS是一个单点登录框架,即Central Authentication Service(中心认证服务) ,开始是由耶鲁大学的一个组织开发,后来归到apereo去管,github地址:htt ...