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();

    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM