ChannelHandler是Netty應用程序的關鍵元素,因此完全地測試他們應該是你的開發過程的一個標準部分。最佳實踐要求你的測試不只要可以證實你的實現是正確的,並且還要可以很容易地隔離那些因修改代碼而忽然出現的問題。這種類型的測試叫作單元測試。ide
其基本思想是,以儘量小的區塊測試你的代碼,而且儘量地和其餘的代碼模塊以及運行時的依賴相隔離。單元測試
一、EmbeddedChannel概述測試
你已經知道,能夠將ChannelPipeline中的ChannelHandler實現鏈接在一塊兒,以構建你的應用程序的業務邏輯。以前已經解釋過,這種設計支持將任何潛在的複雜處理過程分解爲小的可重用的組件,每一個組件都將處理一個明肯定義的任務或者步驟。this
Netty提供了它所謂的Embedded傳輸,用於測試ChannelHandler。這個傳輸是一種特殊的Channel實現——EmbeddedChannel——的功能。這是實現提供了經過ChannelPipeline傳播事件的簡便方法。編碼
這個想法是直截了當的:將入站數據或者出站數據寫入到EmbeddedChannel中,而後檢查是否有任何東西到達了ChannelPipeline的尾端。以這種方式,你即可以肯定消息是否被編碼或者被解碼過了,以及是否觸發了任何的ChannelHandler動做。設計
下圖展現了使用EmbeddedChannel的方法,數據是如何流經ChannelPipeline的。你可使用writeOutbound()方法將消息寫到Channel中,並經過ChannelPipeline沿着出站的方向傳遞。隨後,你可使用readOutbound()方法來讀取已被處理過的消息,已肯定結果是否和預期同樣。相似地,對於入站數據,你須要使用writeInbound()和readInbound()方法。netty
在每種狀況下,消息都將會傳遞過ChannelPipeline,而且被相關的ChannelInboundHandler或者ChannelOutboundHandler處理。若是消息沒有被消費,那麼你可使用readInbound()或者readOutbound()方法來在處理過了這些消息以後,酌情把它們從Channel中讀出來。code
二、使用EmbeddedChannel測試ChannelHandlerblog
JUnit斷言事件
org.junit.Assert 類提供了不少用於測試的靜態方法。失敗的斷言將致使一個異常被拋出,並將終止當前正在執行中的測試。導入這些斷言的最高效的方式是經過一個import static語句來實現:
import static org.junit.Assert.*;
一旦這樣作了,就能夠直接調用Assert方法了:
assertEquals(buf.readSlice(3),read);
三、測試入站消息
下圖展現了一個簡單的ByteToMessageDecoder實現。給定足夠的數據,這個實現將產生固定大小的幀。若是沒有足夠的數據可供讀取,它將等待下一個數據塊的到來,並將再次檢查是否產生一個新的幀。
能夠從圖中右側的幀看到的那樣,這個特定的解碼器將產生固定爲3字節大小的幀。所以,它可能會須要多個事件來提供足夠的字節數以產生一個幀。
最終,每一個幀都會被傳遞給ChannelPipeline中的下一個ChannelHandler,該解碼器的實現以下代碼所示。
//擴展ByteToMessageDecoder以處理入站字節,並將它們解碼爲消息public class FixedLengthFrameDecoder extends ByteToMessageDecoder{ private final int frameLength; //指定要生成的幀的長度 public FixedLengthFrameDecoder(int frameLength) throws IllegalAccessException { if (frameLength <= 0){ throw new IllegalAccessException("frameLength must be a positive integer: " + frameLength); } this.frameLength = frameLength; } @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { //檢查是否有足夠的字節能夠被讀取,以生成下一個幀 while (byteBuf.readableBytes() >= frameLength){ //從ByteBuf中讀取一個新幀 ByteBuf buf = byteBuf.readBytes(frameLength); //將該幀添加到已被解碼的消息列表中 list.add(buf); } } }
如下代碼展現了一個使用EmbeddedChannel的對於前面代碼的測試
public class FixedLengthFrameDecoderTest { @Test public void decode() throws Exception { //建立一個ByteBuf,並存儲9個字節 ByteBuf buf = Unpooled.buffer(); for (int i = 0; i < 9; i++){ buf.writeByte(i); } ByteBuf input = buf.duplicate(); //建立一個EmbeddedChannel,並添加一個FixedLengthFrameDecoder,其將以3字節的幀長度被測試 EmbeddedChannel channel = new EmbeddedChannel(new FixedLengthFrameDecoder(3)); //write bytes //將數據寫入EmbeddedChannel assertTrue(channel.writeInbound(input.retain())); //標記Channel爲已完成狀態 assertTrue(channel.finish()); //read messages //讀取所生成的消息,而且驗證是否有3幀,其中每幀都爲3字節 ByteBuf 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 AbsIntegerEncoder extends MessageToMessageEncoder<ByteBuf>{ @Override protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { //檢查是否有足夠的字節用來編碼 while (byteBuf.readableBytes() >= 4){ //從輸入的ByteBuf中讀取下一個整數,而且計算其絕對值 int value = Math.abs(byteBuf.readInt()); //將該整數寫入到編碼消息的List中 list.add(value); } } }
如下代碼使用了EmbeddedChannel來測試代碼
public class AbsIntegerEncoderTest { @Test public void encode() throws Exception { //建立一個ByteBuf,而且寫入9個負整數 ByteBuf buf = Unpooled.buffer(); for (int i = 1; i < 10; i++){ buf.writeInt(i * -1); } //建立一個EmbeddedChannel,並安裝一個要測試的AbsIntegerEncoder EmbeddedChannel channel = new EmbeddedChannel( new AbsIntegerEncoder()); //寫入ByteBuf,並斷言調用readOutbound()方法將會產生數據 assertTrue(channel.writeOutbound(buf)); assertTrue(channel.finish()); //讀取所產生的消息,並斷言它們包含了對應的絕對值 //read bytes for (int i=1; i < 10; i++){ assertEquals(i , channel.readOutbound()); } assertNull(channel.readOutbound()); } }
五、測試異常處理
應用程序一般須要執行比轉換數據更加複雜的任務。例如,你可能須要處理格式不正確的輸入或者過量的數據。下一個示例中,若是所讀取的字節數超出了特定的限制,咱們將會拋出一個TooLongFrameException。這是一種常常用來防範資源被耗盡的方法。
以下圖,最大的幀大小已經被設置爲3字節,若是一個幀的大小超過了該限制,那麼程序將會丟棄它的字節,並拋出一個TooLongFrameException。位於ChannelPipeline中的其它ChannelHandler能夠選擇在exceptionCaught()方法中處理該異常或者忽略它。
其實現以下代碼所示。
public class FrameChunkDecoder extends ByteToMessageDecoder{ private final int maxFrameSize; public FrameChunkDecoder(int maxFrameSize) { this.maxFrameSize = maxFrameSize; } @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception { int readableBytes = in.readableBytes(); //若是該幀太大,則丟棄它並拋出異常 if (readableBytes > maxFrameSize){ //discard the bytes in.clear(); throw new TooLongFrameException(); } //從ByteBuf中讀取一個新的幀 ByteBuf buf = in.readBytes(readableBytes); //將該幀添加到解碼消息的List中 out.add(buf); } }
使用的Try/Catch塊是EmbeddedChannel的一個特殊功能。若是其中一個write*方法產生了一個受檢查的Exception,那麼它將會被包裝在一個RuntimeException中並拋出,這使得能夠容易地測試出一個Exception是否在處理數據的過程當中已經被處理了。