netty Unit testing

简介

本章主要覆盖以下几方面,单元测试,EmbeddedChannel的简介,使用EmbeddedChannel测试ChannelHandlers

ChannelHandlers是netty应用模块中最重要的一部分,因此彻底的测试在开发过程中也是标准的一部分,很多最好的事件表名你的测试并不能证明你的实现是正确的,但是使用Unit Testing 总是很容易隔离问题。

EmbeddedChannel 概述

Netty中提供了一个叫做embedded transport 为了测试ChannelHandlers,这个具有传输特性的channel实现,EmbeddedChannel,提供一种简单的方式让事件通过整个Pipeline

这种注意是很直接的,你可以用EmbeddedChannel写入站与出站的数据,然后检测通过ChannelPipeline的数据是否符合预期

netty-9

以下为具体的流程图

netty-9

入站数据由ChannelInboundHandlers 处理并且表达数据是从远端处理,出站数据是由ChannelOutboundHandlers 并且表示数据是由远端写的,取决于你所需要测试的channleHandler

使用EmbeddedChannel测试ChannelHandlers

测试入站数据

以下为表示了一个简单的ByteToMessageDecoder的实现,给与充足的数据,它将提供一个固定的大小,如果没有足够的数据去读,它将会等待下个数据块并且再次检查是否有一个固定大小的对象生成

netty-9

你可以看到上面这张图右边的部分,特别是解码生产一个固定大小的对象,因此它可能需要超过一个时间提供足够的字节生产对象。最终每一个对象都将通过下一个ChannelHandler直至通过整个链条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
while (in.readableBytes() >= frameLength) {
ByteBuf buf = in.readBytes(frameLength);
out.add(buf);
}
}
}

以下为测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class FixedLengthFrameDecoderTest {
@Test
public void testFramesDecoded() {
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));
// write bytes
assertTrue(channel.writeInbound(input.retain()));
assertTrue(channel.finish());

// read messages
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();
}

@Test
public void testFramesDecoded2() {
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));
assertFalse(channel.writeInbound(input.readBytes(2)));
assertTrue(channel.writeInbound(input.readBytes(7)));

assertTrue(channel.finish());
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();
}
}

测试出站数据

以下主要以一个MessageToMessage的编码器为例,将负整数转换为整整数
netty-9

1
2
3
4
5
6
7
8
9
10
11
public class AbsIntegerEncoder extends
MessageToMessageEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext,
ByteBuf in, List<Object> out) throws Exception {
while (in.readableBytes() >= 4) {
int value = Math.abs(in.readInt());
out.add(value);
}
}
}

1.写负整数(四个字节的数据到ByteBuf)
2.创建一个EmbeddedChannel并且分配了一个Encoder
3.调用writeOutbound 写入数据ByteBuf
4.标记channel已经写入完成
5.读取所有的出站数据并且确认是否已经转换为正整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AbsIntegerEncoderTest {
@Test
public void testEncoded() {
ByteBuf buf = Unpooled.buffer();
for (int i = 1; i < 10; i++) {
buf.writeInt(i * -1);
}

EmbeddedChannel channel = new EmbeddedChannel(
new AbsIntegerEncoder());
assertTrue(channel.writeOutbound(buf));
assertTrue(channel.finish());
//
// // read bytes
// for (int i = 1; i < 10; i++) {
// assertEquals(i, channel.readOutbound());
// }
assertNull(channel.readOutbound());
}
}

测试异常处理

主要测试当入站数据的大小超过指定长度,会抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FrameChunkDecoder extends ByteToMessageDecoder {
private final int maxFrameSize;

public FrameChunkDecoder(int maxFrameSize) {
this.maxFrameSize = maxFrameSize;
}

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out)
throws Exception {
int readableBytes = in.readableBytes();
if (readableBytes > maxFrameSize) {
// discard the bytes
in.clear();
throw new TooLongFrameException();
}
ByteBuf buf = in.readBytes(readableBytes);
out.add(buf);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class FrameChunkDecoderTest {
@Test
public void testFramesDecoded() {
ByteBuf buf = Unpooled.buffer();
for (int i = 0; i < 9; i++) {
buf.writeByte(i);
}
ByteBuf input = buf.duplicate();

EmbeddedChannel channel = new EmbeddedChannel(
new FrameChunkDecoder(3));

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 frames
ByteBuf 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();
}
}

这部分主要使用try…catch来测试是否有异常被抛出,因为这个是运行时异常,这个很容易测试是否一个异常在处理数据的过程中被捕捉并且处理的。

总结

以一个侵入少的例如JUnit单元测试是一个极度有效的方式保证代码的正确性并且增强可维护性。在这章,你可以学到netty提供的工具怎么测试您自己的ChannelHandler

在下一章您将聚焦于怎么用netty写一个真实的应用。我们将不会表示更多的测试用例,希望你自己保持思考我们想表达的测试方法的重要性。

坚持原创技术分享,您的支持将鼓励我继续创作!