Netty 框架學習 —— 單元測試
EmbeddedChannel 概述
ChannelHandler 是 Netty 程式的關鍵元素,所以徹底地測試它們應該是你的開發過程中的一個標準部分,EmbeddedChannel 是 Netty 專門為改進針對 ChannelHandler 的單元測試而提供的。Netty 提供了它所謂的 Embedded 傳輸,這個傳輸是 EmbeddedChannel 的功能,提供了通過 ChannelPipeline 傳播事件的簡便方法
這個方法是:將入站資料或者出站資料寫入到 EmbeddedChannel 中,然後檢查是否有任何東西到達 CHannelPipeline 的尾端。通過這種方式,你可以確定訊息是否已經被編碼或者解碼過,以及是否觸發了任何 ChannelHandler 動作
下表列出了 EmbeddedChannel 的相關方法
入站資料由 ChannelInboundHandler 處理,代表從遠端節點讀取的資料。出站資料由 ChannelOutboundHandler 處理,代表將要寫到遠端節點的資料。根據你要測試的 ChannelHandler,你可以使用 Inbound() 或者 Outbound() 方法對,或者兼而有之
下圖展示了使用 EmbeddedChannel 的方法,資料是如何流經 ChannelPipeline 的。 你可以使用 writeOutbound()方法將訊息寫到 Channel 中,並通過 ChannelPipeline 沿 著出站的方向傳遞。隨後,你可以使用 readOutbound()方法來讀取已被處理過的訊息,以確 定結果是否和預期一樣。類似地,對於入站資料,你需要使用 writeInbound()和 readInbound() 方法
![](G:\SSS\Java\Java SE\部落格\Netty\EmbeddedChannel 的資料流.png)
使用 EmbeddedChannel 測試 ChannelHandler
1. 測試入站訊息
下述程式碼展示了一個簡單的 ByteToMessageDecoder 實現,給定足夠的資料,這個實現將產生固定大小的幀。如果沒有足夠的資料可供讀取,它將等待下一個資料塊的到來,並將再次檢查是否能夠產生一個新的幀
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 中讀取一個新幀 ByteBuf buf = in.readBytes(frameLength); //將該幀新增到已被解碼的訊息列表中 out.add(buf); } } }
下述程式碼展示了使用 EmbeddedChannel 的對於前面程式碼的測試
public class FixedLengthFrameDecoderTest {
@Test
public void testFrameDecoded() {
//建立一個 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));
//將資料寫入 EmbeddedChannel
System.out.println(channel.writeInbound(input.retain()));//true
//標記 Channel 為已完成狀態
System.out.println(channel.finish());//true
//讀取所生成的訊息,並且驗證是否有 3 幀,其中每幀都為 3 位元組
ByteBuf read = channel.readInbound();
System.out.println(buf.readSlice(3).equals(read));// true
read = channel.readInbound();
System.out.println(buf.readSlice(3).equals(read));// true
read.release();
read = channel.readInbound();
System.out.println(buf.readSlice(3).equals(read));// true
read.release();
System.out.println(channel.readInbound() == null);// true
buf.release();
}
@Test
public void testFramesDescode2() {
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(channel.writeInbound(input.readBytes(2)));// false
System.out.println(channel.writeInbound(input.readBytes(7)));// true
System.out.println(channel.finish());// true
ByteBuf read = channel.readInbound();
System.out.println(buf.readSlice(3) == read);// false
read.release();
read = channel.readInbound();
System.out.println(buf.readSlice(3) == read);// false
read.release();
read = channel.readInbound();
System.out.println(buf.readSlice(3) == read);// false
read.release();
System.out.println(channel.readInbound() == null);// true
buf.release();
}
}
2. 測試入站訊息
測試出站訊息的處理過程和剛才所看到的類似,在下面的例子中,我們將會展示如何使用 EmbeddedChannel 來測試另一個編碼器形式的 ChannelOutboundHandler,編碼器是一種將一種訊息格式轉換為另一種的元件
該示例將會按照下列方式工作:
- 持有 AbsIntegerEncoder 的 EmbeddedChannel 將會以 4 位元組的負整數的形式寫出站資料
- 編碼器將從傳入的 ByteBuf 中讀取每個負整數,並將會呼叫 Math.abs() 方法來獲取其絕對值
- 編碼器將會把每個負整數的絕對值寫到 ChannelPipeline 中
下述程式碼展示了這個邏輯
public class AbsIntegerEncoder extends MessageToMessageEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
while (msg.readableBytes() >= 4) {
//從輸入的 ByteBuf 中讀取下一個整數,並且計算其絕對值
int value = Math.abs(msg.readInt());
//將該整數寫入到編碼訊息的 List 中
out.add(value);
}
}
}
使用 EmbeddedChannel 來測試程式碼
public class AbsIntegerEncoderTest {
@Test
public void testEncoded() {
ByteBuf buf = Unpooled.buffer();
for (int i = 1; i < 10; i++) {
buf.writeInt(i * -1);
}
// 建立一個 EmbeddedChanel,並安裝一個要測試的 AbsIntegerEncoder
EmbeddedChannel channel = new EmbeddedChannel(new AbsIntegerEncoder());
// 寫入 ByteBuf,呼叫 readOutbound() 方法將會產生資料
System.out.println(channel.writeOutbound(buf));
System.out.println(channel.finish());
channel.readOutbound();
for (int i = 1; i < 10; i++) {
int temp = channel.readOutbound();
System.out.println(temp);
}
System.out.println(channel.readOutbound() == null);
}
}
下面是程式碼中執行的步驟。
- 將 4 位元組的負整數寫到一個新的 ByteBuf 中
- 建立一個 EmbeddedChannel,併為它分配一個 AbsIntegerEncoder
- 呼叫 EmbeddedChannel 上的 writeOutbound()方法來寫入該 ByteBuf
- 標記該 Channel 為已完成狀態
- 從 EmbeddedChannel 的出站端讀取所有的整數,並驗證是否只產生了絕對值
測試異常處理
應用程式通常需要執行比轉換資料更加複雜的任務。例如,你可能需要處理格式不正確的輸 入或者過量的資料。在下一個示例中,如果所讀取的位元組數超出了某個特定的限制,我們將會丟擲一個 TooLongFrameException,這是一種經常用來防範資源被耗盡的方法
實現的程式碼如下
// 擴充套件 ByteToMessageDecoder 以將入站位元組碼為訊息
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) {
// 如果該幀太大,則丟棄它並丟擲一個 TooLongFrameException
in.clear();
throw new TooLongFrameException();
}
// 否則,從 ByteBuf 中讀取一個新的幀
ByteBuf buf = in.readBytes(readableBytes);
// 該幀新增到解碼訊息的List中
out.add(buf);
}
}
再使用 EmbeddedChannel 來測試這段程式碼
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,並向其安裝一個幀大小為 3 位元組的 FixedLengthFrameDecoder
EmbeddedChannel channel = new EmbeddedChannel(new FrameChunkDecoder(3));
System.out.println(channel.writeInbound(input.readBytes(2)));
try {
// 寫入一個 4 位元組大小的幀,並捕獲預期的異常
channel.writeInbound(input.readBytes(4));
} catch (TooLongFrameException e) {
e.printStackTrace();
}
// 寫入剩餘的 2 位元組,會產生一個有效幀
System.out.println(channel.writeInbound(input.readBytes(3)));//true
System.out.println(channel.finish());
// 讀取產生的訊息,並且驗證值
ByteBuf read = channel.readInbound();
System.out.println(read.equals(buf.readSlice(2)));//true
read.release();
read = channel.readInbound();
System.out.println(read.equals(buf.skipBytes(4).readSlice(3)));//true
read.release();
buf.release();
}
}