Netty in Action (十九) 第九章节 单元测试

本章内容包括:

1)单元测试

2)EmbeddedChannel的说明

3)使用EmbeddedChannel测试ChannelHandler

对于一个Netty应用来说,ChannelHandler是一个至关重要的元素,所以充分地去测试ChannelHandler应该是你开发过程中必要的组成部分,我们的最佳实践会告诉你测试不仅能保证你的具体实现的正确性,更重要的是,当你突然变更你的代码的时候,它能很容易地去隔离问题,这种类型的测试叫做单元测试

尽管对单元测试大家没有一个完整的定义,但是更多的参与者觉得单元测试是基础的,是必要的,单元测试的理念基本上是这样:测试尽可能小的代码块,尽可能的隔离其他模块或者是运行时的依赖,数据库网络等带来的影响,如果你验证每一个单元模块的它本身的正确性的话,你会发现当出现bug时,你会很容易找到问题的根源

在这个章节,我们将学习一个特殊的Channel的实现-----EmbededChannel,这个类是由Netty提供的,指定用来构建对ChannelHandler的单元测试的基本建设类

因为代码模块或者代码单元将会脱离它正常的运行环境去做测试,或者你需要一个框架或者工具来运行测试它,在我们所有的例子中,我们使用JUnit4作为我们的测试框架,所以我们需要对JUnit4有着一些基本的了解,如果你对JUnit4完全不了解的话,不要慌张,JUnit4是简单且强大的框架,你可以去www.junit.org网站上去稍微了解一下Junit4一些基本的知识,在这个网站里有你想要知道的一切

9.1 Overview of EmbeddedChannel

你已经掌握了ChannelHandler的一些具体实现可以链式的放入ChannelPipeline中用来构建你应用的业务逻辑模块的实现,我们之前也解释过,这种设计可以把一些潜在的复杂的业务逻辑处理分解成迷你的可重复利用的组件,分解后的Handler都是一个明确的任务或者步骤,在这个章节中,我们也会教你如何简化测试

Netty提供了一个名为嵌入式的传输服务用来测试ChannelHandler,这个传输服务是特殊Channel实现EmbededChannel的一个特性,它可以提供一个简单的方式来让所有的事件通过管道

这个想法是直接有效的,你写入输入输出数据到EmbededChannel中,然后检测是否有内容能够正确地传输到ChannelPipeline的末端,通过这个方法你可以确定消息是否被正确的编码或者解码,是否每一个ChannelHandler定义的动作都被触发执行了

EmbededChannel的一些相关的方法在表9.1中展示了

输入数据将被ChannelInboundHandler处理代表着从远程端接收数据,输出数据代表被ChannelOutboundChannel处理,代表发送数据到远程端,在测试过程中你可能使用*Inbound或者*Outbound类似这样的方法去测试或者两种方法都使用,这都依赖于你测试的是哪种ChannelHandler

图9.1展示了使用EmbededChannel的方法的时候,数据如何通过ChannelPipeline的情形,你可以使用writeOutbound方法去写入一个message到Channel中,然后这个信息会以输出方向通过ChannelPipeline,随后你可以用readOutbound方法去读取被处理的信息,来确定读取的结果是否与我们预期的结果一样,简而言之,对于输入数据,你可以使用writeInbound或者readInbound方法

在每一个案例中,信息在通过ChannelPipeline中的时候,都是被相关的Handler处理的,要么是ChannelInboundHandler要么是ChannelOutboundHandler,如果消息还没有被消费,你可以使用readInbound或者readOutbound方法在适当的时候读取从channel中出来的信息

让我们详细地讲解一下这两种场景吧,看看他们是如何被应用到你的测试场景中去的

9.2 Testing ChannelHandlers with EmbeddedChannel

在这个小节中,我们将讲解如何使用EmbededChannel测试一个ChannelHandler

TIPS:JUnit assertions

org.junit.Assert类提供了很多静态的方法用来测试,一个失败的断言将会导致一个异常被抛出,并且会终止当前的测试,我们可以使用静态导入的方式来引入这些断言,这将会是一个很高效的方法

9.2.1 Testing inbound messages

图92展示了一个简单的ByteToMessageDecoder实现,给予足够的数据,ByteToMessageDecoder会产生出固定大小的帧数据,如果没有足够数量的数据被读取,它将会等待下一个块中的数据,等下一个块数据到达的时候,再次判断是否可以生成出一个固定大小的帧数据

我们可以看出右边的帧数据的图形,这个特制的解码器可以生成出3个字节固定大小的帧数据,也就是说,当一个事件触发的时候需要带来足够的字节数来生产一个帧数据

最后生产出来的帧数据会被传播到ChannelPipeline中的下一个管道中去

这个解码器的具体实现如下面的代码清单所示:

现在我们编写一个单元测试类来测试这个解码器是否能够按照我们预期的一样去执行,我们之前就提过很多次,即使是一个很简单的代码块,单元测试可以帮助我们在代码重构的期间阻止一些问题的发生或者在问题发生的时候,可以很轻松地诊断出问题的所在

下面是使用EmbededChannel来测试上面解码器的代码

方法testFramesDecoded验证了当一个ByteBuf中包含9个可读字节的时候,可以被分成3个全新的ByteBuf,每一个ByteBuf中包含3个字节,注意到一个拥有9个字节的ByteBuf调用了writeInboud方法,然后finish方法被执行标志着EmbededChannel完成,最后readInbound方法被调用用来从EmbededChannel中精确地读取三个帧数据和一个null值

testFramesDecoded2方法是简单的,与方法一只有唯一的一点不同,在输入ByteBuf的写入的时候分了2个步骤,当writeInbound(input.readBytes(2))方法被调用的时候,返回了false,这是为什么呢?我们在表9.1中陈述过,如果对于后来对应的readInboud方法能够返回数据,那么writeInbound方法将会返回true,但是我们这个案例中的FixedLengthFrameDecoder只会在输入的字节数等于或者超过3个字节的时候才会产出数据,接下来的测试就会与testFramesDecoded一样了

9.2.2 Testing outbound messages

测试输出信息的过程与我们刚刚写的输入信息测试是很相似的,在接下来的一个例子中,我们将向你展示如何使用EmbededChannel去测试一个编码器类型的ChannelOutboundHandler,这个组件可以将一个信息从一个格式转化成另一个格式,我们将会在下面的一个章节详细地讲解编码器和解码器,目前为止,我们只是在我们测试过程中,简单地提及一下,AbsIntegerEncoder,这是Netty的MessageToMessageEncoder的一个具体实现,用来将负值的Integer转化成它的绝对值

这个例子会如下工作:

1)一个EmbededChannel会持有一个AbsIntegerEncoder,会写入四个字节的负值Integer类型的数据

2)解码器将会从每个ByteBuf中读取负值,然后调用Math.abs()方法来获取他们的绝对值

3)解码器会将每个Integer的绝对值写入到ChannelPipeline中

图9.3展示了这个逻辑

下面的代码清单实现了图9.3的逻辑,encode方法将生产的值放入到List中

下面的代码清单使用EmbededChannel测试了上面的代码块

在这段代码中有一下几个比较重要的步骤:

①写入四字节的负值Integer类型的变量到ByteBuf中

②创建一个EmbededChannel并且分配一个AbsIntegerEncoder给EmbededChannel

③在EmbededChannel中调用writeOutChannel方法写入ByteBuf

④标记channel已经完成

⑤从EmbededChannel的输出端读取所有的Integer类型,然后验证输出数据是不是全部是绝对值

9.3 Testing exception handling

在应用中除了要处理传输数据之外,还会有一些其他的任务,例如,你有时候需要处理畸形数据或者超大数据,在下面的一个例子中,如果读取的字节的数量超过了我们固定的一个限制时,我们会抛出一个TooLongFrameException的异常,这种方式经常使用来防止资源短缺

在图9.4中,我们规定最大的帧数据的大小不能超过三个字节,如果某个帧数据的大小超过了这个限制的话,它的字节数将被消除且会将TooLongFrameException的异常抛出,

在管道中的其他ChannelHandler可以去处理这个异常也可以直接忽视这个异常

下面的代码给出了具体的实现

同样,我们再次使用EmbededChannel测试一下上面的代码

如果对上面的代码只是轻轻一瞥的话,你会发现这个代码与9.2的代码清单看起来很类似,但只有一个小小的不同,其实就是对TooLongFrameException的处理不同,在这里我们用try/catch块包裹住这个代码块其实是EmbededChannel的一个特性,如果其中一个write方法抛出了一个异常的话,它将会以运行时异常的形式抛出,这让测试将变得很简单,因为你可以选择是否在处理数据的过程中处理异常

在这里测试方法是可以用在任何可以抛出异常的ChannelHandler的实现里的

9.4 Summary

用专业的测试工具JUnit来做单元测试会是一个极佳的方法来保证你的代码的正确性并且会提高你代码的可维护性,在这个章节中我们学些了Netty提供的一些测试工具类来测试你自定义的ChannelHandler

在下一个章节中,我们将聚焦于写出一个真实可用的Netty应用,我们将不会再展示任何测试的代码类,所以我们希望你将这个章节讲解的测试方法牢记心中

时间: 2024-12-16 12:39:14

Netty in Action (十九) 第九章节 单元测试的相关文章

Welcome to Swift (苹果官方Swift文档初译与注解十九)---123~132页(第二章..本章节还剩6页)

Working with Characters (与字符相关) 在Swift中,String类型表示一组有序字符的值.每个字符都是一个Unicode符号.可以使用for-in循环来遍历字符串中的每个字符: for character in "Dog!??" {   println(character) } // D // o // g // ! // ?? 在Swift中也可以使用Character类型来显式的创建一个单字符的常量或者变量: let yenSign: Character

“全栈2019”Java第九十九章:局部内部类与继承详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第九十九章:局部内部类与继承详解 下一章 "全栈2019"Java第一百章:局部内部类可以实现接口吗? 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学习小

Gradle 1.12翻译——第十九章. Gradle 守护进程

有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com/1.12/userguide/userguide.html 本文原创,转载请注明出处:http://blog.csdn.net/maosidiaoxian/article/details/41343615 关于我对Gradle的翻译,以Github上的项目及http://gradledoc.qin

【WPF学习】第十九章 控件类

原文:[WPF学习]第十九章 控件类 WPF窗口充满了各种元素,但这些元素中只有一部分是控件.在WPF领域,控件通常被描述为与用户交互的元素--能接收焦点并接受键盘或鼠标输入的元素.明显的例子包括文本框和按钮.然而,这个区别有时有些模糊.将工具提示视为控件,因为它根据用户鼠标的移动显示或消失.将标签视为控件,因为它支持记忆码(mnemonics,将焦点转移到相关控件快捷键). 所有控件都继承自System.Windows.Control类,该类添加了一小部分基本的基础结构: 设置控件内容对齐方式

Python之路【第十九篇】:爬虫

Python之路[第十九篇]:爬虫 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模拟程序或者蠕虫. Requests Python标准库中提供了:urllib.urllib2.httplib等模块以供Http请求,但是,它的 API 太渣了.它是为另一个时代.另一个互联网所创建的.它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务. import

《Netty in action》目录修复版本分享

最近阅读了Netty in action一书.深感外国友人的书籍编写能力强大.作者由简入深.精简描述了Netty的相关知识,如何使用等等. 本来想翻译一下的.尝试着翻译了一点之后.发现非常痛苦啊.ps.笔者英语不是很好. 很多时候需要自己先把英文理解成中文然后再拼凑起来.写出来的中文译文感觉好奇怪.所以就不拿出来献丑了.把网上的PDF电子书分享一下. 前段时间去找的电子书目录都存在问题的. NIA一书分为四个部分.16个章节.网上的第五版书籍是完整的.但是目录结构完全错误了.再次使用软件修复了.

第十九章 APO连接与网络v节点

                  第十九章   APO连接与网络v节点       在编写网络底层实现前,需要做许多的准备工作:除了修改前面的章节外,可能还需写3章:本章.文件号管理类的实现.本地内存管理类的实现.我很希望最终的网络编程能给我惊喜:IP/TCP/UDP/ICMP的实现.包括所有的网络服务器的实现(HTTP.DNS.FTP等等),能到达300行的代码量:多于300行代码量那就更好了.我会很高兴.网络编程第一层简为socket层,第二层简称为TCP层,第三层简称为IP层:以后,不再

我的编程之路(十九) 开发中一些细节与启发

1.js的命名空间           如果写后台代码,分层是潜意识中的基本常识,但是一到了前台,却没了这种意识,归根结底还是js用的不多,也一直没有在意js的地位,直到现在富客户端的趋势与要求,使得很多代码都要在前台用js或其框架完成,所以对于js代码的管理就要像后台java代码一样有其规范了,而命名空间就是package,也是为了管理不同层次的代码. 2.闭包          闭包就是能够读取其他函数内部变量的函数.它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值

十九、android中判断sim卡状态和读取联系人资料的方法

在写程序中,有时候可能需要获取sim卡中的一些联系人资料.在获取sim卡联系人前,我们一般会先判断sim卡状态,找到sim卡后再获取它的资料,如下代码我们可以读取sim卡中的联系人的一些信息. PhoneTest.java package com.android.test; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.datab