Read book Netty in action(Chapter X)--Unit Testing
创始人
2024-05-30 00:12:13
0

序言

ChannelHandler 是Netty 应用程序的关键元素,所以彻底地测试它们应该是你的开发过程的一个标准部分。最佳实践要求你的测试不仅要能够证明你的实现是正确的,而且还要能够很容易地隔离那些因修改代码而突然出现的问题。这种类型的测试叫作单元测试。
单元测试应该做到应盖尽盖!
虽然单元测试没有统一的定义,但是大多数的从业者都有基本的共识。其基本思想是,以尽可能小的区块测试你的代码,并且尽可能地和其他的代码模块以及运行时的依赖(如数据库和网络)相隔离。如果你的应用程序能通过测试验证每个单元本身都能够正常地工作,那么在出了问题时将可以更加容易地找出根本原因。

EmbeddedChannel 概述

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

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

EmbeddedChannel
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 处理,代表将要写到远程节点的数据。根据你要测试的ChannelHandler,你将使用Inbound()或Outbound()方法对,或者兼而有之。

使用EmbeddedChannel 测试ChannelHandler

JUnit 断言
org.junit.Assert 类提供了很多用于测试的静态方法。失败的断言将导致一个异常被抛出,并将终止当前正在执行中的测试。导入这些断言的最高效的方式是通过一个import static 语句来实现:import static org.junit.Assert.*;一旦这样做了,就可以直接调用Assert 方法了:assertEquals(buf.readSlice(3), read);

测试入站消息

给定一个ByteToMessageDecoder的实现,给定足够的数据,这个实现将
产生固定大小的帧。如果没有足够的数据可供读取,它将等待下一个数据块的到来,并将再次检查是否能够产生一个新的帧。

最终,每个帧都会被传递给ChannelPipeline 中的下一ChannelHandler。

这是该解码器的实现:

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;}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {if (in.readableBytes() >= frameLength){ByteBuf byteBuf = in.readBytes(frameLength);out.add(byteBuf);}}
}
 

现在,让我们创建一个单元测试,以确保这段代码将按照预期执行。正如我们前面所指出的,即使是在简单的代码中,单元测试也能帮助我们防止在将来代码重构时可能会导致的问题,并且能在问题发生时帮助我们诊断它们。

 public void testFramesDecoded(){ByteBuf buffer = Unpooled.buffer();for (int i = 0; i < 99; i++) {buffer.writeByte(i);}ByteBuf duplicate = buffer.copy();FixedLengthFrameDecoder fixedLengthFrameDecoder = new FixedLengthFrameDecoder(3);EmbeddedChannel embeddedChannel = new EmbeddedChannel(fixedLengthFrameDecoder);assertTrue(embeddedChannel.writeInbound(duplicate.retain()));assertTrue(embeddedChannel.finish());ByteBuf read =  embeddedChannel.readInbound();duplicate.readBytes(3);read.release();}

该testFramesDecoded()方法验证了:一个包含99 个可读字节的ByteBuf 被解码为33个ByteBuf,每个都包含了3 字节。需要注意的是,仅通过一次对writeInbound()方法的调用,ByteBuf 是如何被填充了99 个可读字节的。在此之后,通过执行finish()方法,将EmbeddedChannel 标记为了已完成状态。最后,通过调用readInbound()方法,从EmbeddedChannel 中正好读取了33个帧和一个null。

测试出站消息

测试出站消息的处理过程和刚才所看到的类似。在下面的例子中,我们将会展示如何使用EmbeddedChannel 来测试一个编码器形式的ChannelOutboundHandler,编码器是一种将一种消息格式转换为另一种的组件 – AbsIntegerEncoder

持有AbsIntegerEncoder 的EmbeddedChannel 将会以4 字节的负整数的形式写出站数据;
编码器将从传入的ByteBuf 中读取每个负整数,并将会调用Math.abs()方法来获取其绝对值;
编码器将会把每个负整数的绝对值写到ChannelPipeline 中。

@Testpublic void testEnc(){ByteBuf buf = Unpooled.buffer();for (int i = 1; i < 10; i++) {buf.writeInt(i * -1);}EmbeddedChannel embeddedChannel = new EmbeddedChannel(new AbsIntegerEncoder());Assert.assertTrue(embeddedChannel.writeOutbound(buf));Assert.assertTrue(embeddedChannel.finish());for (int i = 1; i < 10; i++) {Assert.assertTrue(embeddedChannel.readOutbound().equals(i));}Assert.assertNull(embeddedChannel.readOutbound());}

将4 字节的负整数写到一个新的ByteBuf 中。
创建一个EmbeddedChannel,并为它分配一个AbsIntegerEncoder。
调用EmbeddedChannel 上的writeOutbound()方法来写入该ByteBuf。
标记该Channel 为已完成状态。
从EmbeddedChannel 的出站端读取所有的整数,并验证是否只产生了绝对值。

测试异常处理

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

之前我们曾写过一个编码器FixedLengthFrameDecoder,如果最大的帧大小已经被设置为3 字节。如果一个帧的大小超出了该限制,那么程序将
会丢弃它的字节,并抛出一个TooLongFrameException。位于ChannelPipeline 中的其他ChannelHandler 可以选择exceptionCaught()方法中处理该异常或者忽略它。

 @Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {int readableBytes = in.readableBytes();if (readableBytes > frameLength) {in.clear();throw new TooLongFrameException();}ByteBuf byteBuf = in.readBytes(readableBytes);out.add(byteBuf);}
 
@Testpublic void testFramesDecodedMax() {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));Assert.assertTrue(channel.writeInbound(input.readBytes(2)));try {channel.writeInbound(input.readBytes(4));Assert.fail();} catch (TooLongFrameException e) {// expected exception}assertTrue(channel.writeInbound(input.readBytes(3)));assertTrue(channel.finish());// Read framesByteBuf read = (ByteBuf) channel.readInbound();assertEquals(buf.readSlice(2), read);read.release();read = (ByteBuf) channel.readInbound();assertEquals(buf.skipBytes(4).readSlice(3), read);read.release();buf.release();}

乍一看,这看起来非常类似于前面代码的测试,但是它有一个有趣的转折点,即对TooLongFrameException的处理。这里使用的try/catch块是EmbeddedChannel的一个特殊功能。如果其中一个write方法产生了一个受检查的Exception,那么它将会被包装在一个RuntimeException中并抛出。这使得可以容易地测试出一个Exception是否在处理数据的
过程中已经被处理了。

结束语

使用JUnit 这样的测试工具来进行单元测试是一种非常行之有效的方式,它能保证你的代码的正确性并提高它的可维护性。为我们学习了如何使用Netty 提供的测试工具来测试自定义的ChannelHandler。

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...