Netty實戰九之單元測試

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

Netty實戰九之單元測試

二、使用EmbeddedChannel測試ChannelHandlerblog

JUnit斷言事件

org.junit.Assert 類提供了不少用於測試的靜態方法。失敗的斷言將致使一個異常被拋出,並將終止當前正在執行中的測試。導入這些斷言的最高效的方式是經過一個import static語句來實現:

import static org.junit.Assert.*;

一旦這樣作了,就能夠直接調用Assert方法了:

assertEquals(buf.readSlice(3),read);

三、測試入站消息

下圖展現了一個簡單的ByteToMessageDecoder實現。給定足夠的數據,這個實現將產生固定大小的幀。若是沒有足夠的數據可供讀取,它將等待下一個數據塊的到來,並將再次檢查是否產生一個新的幀。

Netty實戰九之單元測試
能夠從圖中右側的幀看到的那樣,這個特定的解碼器將產生固定爲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中。
Netty實戰九之單元測試
如下代碼實現了這個邏輯。

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()方法中處理該異常或者忽略它。
Netty實戰九之單元測試
其實現以下代碼所示。

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是否在處理數據的過程當中已經被處理了。

相關文章
相關標籤/搜索