今天分享 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 字节大小的帧。因此,它可能会需要多个事件来提供足够的字节数以产生一个帧。 测试类: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 中。 测试类: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()方法中处理该异常或者忽略它。 测试类: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协议,敬请期待!