第十章:單元測試代碼

本章介紹java

單元測試ide

EmbeddedChannel單元測試

會了使用一個或多個ChannelHandler處理接收/發送數據消息,可是如何測試它們呢?Netty提供了2個額外的類使得測試ChannelHandler變得很容易,本章講解如何測試Netty程序。測試使用JUnit4,若是不會用能夠慢慢了解。JUnit4很簡單,可是功能很強大。本章將重點講解測試已實現的ChannelHandler和編解碼器。測試

10.1 Generalthis

Netty提供了一個簡單的方法在ChannelPipeline上「堆疊」不一樣的ChannelHandler實現。全部的ChannelHandler都會參與處理事件,這個設計容許獨立出可重用的小邏輯塊,它只處理一個任務。這不只使代碼更清晰,也更容易測試。編碼

測試ChannelHandler能夠經過使用「嵌入式」傳輸很容易的傳遞事件槽管道以測試你的實現。.net

Netty提供了一個特定的Channel實現:EmbeddedChannel。設計

它是如何工做的呢?EmbeddedChannel的工做很是簡單,它容許寫入入站或出站數據,而後檢查ChannelPipeline的結束。這容許你檢查消息編碼/解碼或觸發ChannelHandler任何行爲。netty

編寫入站和出站的卻別是什麼?code

入站數據是經過ChannelInboundHandler處理,表明從遠程對等通道讀取數據;出站數據是經過ChannelOutboundHandler處理,表明寫入數據到遠程對等通道。所以測試ChannelHandler就會選擇writeInbound(...)或writeOutbound()(或者都選擇)。

EmbeddedChannel提供了下面一些方法:

writeInbound(Object...),寫一個消息到入站通道

writeOutbound(Object...),寫消息到出站通道

readInbound(),從EmbeddedChannel讀取入站消息,可能返回null

readOutbound(),從EmbeddedChannel讀取出站消息,可能返回null

finish(),標示EmbeddedChannel已結束,任何寫數據都會失敗

爲了更清楚的瞭解其處理過程,看下圖:

如上圖所示,使用writeOutbound(...)寫消息到通道,消息在出站方法經過ChannelPipeline,以後就可使用readOutbound()讀取消息。着一樣使用與入站,使用writeInbound(...)和readInbound()。處理入站和出站是類似的,它老是遍歷整個ChannelPipeline直到ChannelPipeline結束,並將處理過的消息存儲在EmbeddedChannel中。

10.2 測試ChannelHandler

        測試ChannelHandler最好的選擇是使用EmbeddedChannel。

10.2.1 測試處理入站消息的handler

咱們來編寫一個簡單的ByteToMessageDecoder實現,有足夠的數據能夠讀取時將產生固定大小的包,若是沒有足夠的數據能夠讀取,則會等待下一個數據塊並再次檢查是否能夠產生一個完整包。下圖顯示了從新組裝接收的字節:

如上圖所示,它可能會佔用一個以上的「event」以獲取足夠的字節產生一個數據包,並將它傳遞到ChannelPipeline中的下一個ChannelHandler,看下面代碼:

package netty.in.action;  
import java.util.List;  
import io.netty.buffer.ByteBuf;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.handler.codec.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 buf = in.readBytes(frameLength);  
            out.add(buf);  
        }  
    }  
}

解碼器的實現完成了,寫一個單元測試的方法是個好主意。即便代碼看起來沒啥問題,可是也應該進行單元測試,這樣能在部署到生產以前就發現問題。如今讓咱們來看看如何使用EmbeddedChannel來完成測試,看下面代碼:

package netty.in.action;  
import io.netty.buffer.ByteBuf;  
import io.netty.buffer.Unpooled;  
import io.netty.channel.embedded.EmbeddedChannel;  
import org.junit.Assert;  
import org.junit.Test;  
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  
        Assert.assertTrue(channel.writeInbound(input));  
        Assert.assertTrue(channel.finish());  
        // read message  
        Assert.assertEquals(buf.readBytes(3), channel.readInbound());  
        Assert.assertEquals(buf.readBytes(3), channel.readInbound());  
        Assert.assertEquals(buf.readBytes(3), channel.readInbound());  
        Assert.assertNull(channel.readInbound());  
    }  
    @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));  
        Assert.assertFalse(channel.writeInbound(input.readBytes(2)));  
        Assert.assertTrue(channel.writeInbound(input.readBytes(7)));  
        Assert.assertTrue(channel.finish());  
        Assert.assertEquals(buf.readBytes(3), channel.readInbound());  
        Assert.assertEquals(buf.readBytes(3), channel.readInbound());  
        Assert.assertEquals(buf.readBytes(3), channel.readInbound());  
        Assert.assertNull(channel.readInbound());  
    }  
}

如上面代碼,testFramesDecoded()方法想測試一個ByteBuf,這個ByteBuf包含9個可讀字節,被解碼成包含了3個可讀字節的ByteBuf。

你可能注意到,它寫入9字節到通道是經過調用writeInbound()方法,以後再執行finish()來將EmbeddedChannel標記爲已完成,最後調用readInbound()方法來獲取EmbeddedChannel中的數據,直到沒有可讀字節。

testFramesDecoded2()方法採起一樣的方式,但有一個區別就是入站ByteBuf分兩步寫的,當調用writeInbound(input.readBytes(2))後返回false時,FixedLengthFrameDecoder值會產生輸出,至少有3個字節是可讀,testFramesDecoded2()測試的工做至關於testFramesDecoded()。

10.2.2 測試處理出站消息的handler

測試處理出站消息和測試處理入站消息不太同樣,例若有一個繼承MessageToMessageEncoder的AbsIntegerEncoder類,它所作的事情以下:

將已接收的數據flush()後將從ByteBuf讀取全部整數並調用Math.abs(...)

完成後將字節寫入ChannelPipeline中下一個ChannelHandler的ByteBuf中

看下圖處理過程:

看下面代碼:

package netty.in.action;  
import java.util.List;  
import io.netty.buffer.ByteBuf;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.handler.codec.MessageToMessageEncoder;  
public class AbsIntegerEncoder extends MessageToMessageEncoder<ByteBuf> {  
    @Override  
    protected void encode(ChannelHandlerContext ctx, ByteBuf msg,  
            List<Object> out) throws Exception {  
        while(msg.readableBytes() >= 4){  
            int value = Math.abs(msg.readInt());  
            out.add(value);  
        }  
    }  
}

下面代碼是測試AbsIntegerEncoder:

package netty.in.action;  
import io.netty.buffer.ByteBuf;  
import io.netty.buffer.Unpooled;  
import io.netty.channel.embedded.EmbeddedChannel;  
import org.junit.Assert;  
import org.junit.Test;  
public class AbsIntegerEncoderTest {  
    @Test  
    public void testEncoded() {  
        //建立一個能容納10個int的ByteBuf  
        ByteBuf buf = Unpooled.buffer();  
        for (int i = 1; i < 10; i++) {  
            buf.writeInt(i * -1);  
        }  
        //建立EmbeddedChannel對象  
        EmbeddedChannel channel = new EmbeddedChannel(new AbsIntegerEncoder());  
        //將buf數據寫入出站EmbeddedChannel  
        Assert.assertTrue(channel.writeOutbound(buf));  
        //標示EmbeddedChannel完成  
        Assert.assertTrue(channel.finish());  
        //讀取出站數據  
        ByteBuf output = (ByteBuf) channel.readOutbound();  
        for (int i = 1; i < 10; i++) {  
            Assert.assertEquals(i, output.readInt());  
        }  
        Assert.assertFalse(output.isReadable());  
        Assert.assertNull(channel.readOutbound());  
    }  
}

10.3 測試異常處理

有時候傳輸的入站或出站數據不夠,一般這種狀況也須要處理,例如拋出一個異常。這多是你錯誤的輸入或處理大的資源或其餘的異常致使。咱們來寫一個實現,若是輸入字節超出限制長度就拋出TooLongFrameException,這樣的功能通常用來防止資源耗盡。看下圖:

上圖顯示幀的大小被限制爲3字節,若輸入的字節超過3字節,則超過的字節被丟棄並拋出TooLongFrameException。在ChannelPipeline中的其餘ChannelHandler實現能夠處理TooLongFrameException或者忽略異常。處理異常在ChannelHandler.exceptionCaught()方法中完成,ChannelHandler提供了一些具體的實現,看下面代碼:

package netty.in.action;  
import java.util.List;  
import io.netty.buffer.ByteBuf;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.handler.codec.ByteToMessageDecoder;  
import io.netty.handler.codec.TooLongFrameException;  
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) {  
            in.clear();  
            throw new TooLongFrameException();  
        }  
        // 讀取ByteBuf並放到List中  
        ByteBuf buf = in.readBytes(readableBytes);  
        out.add(buf);  
    }  
}

測試FrameChunkDecoder的代碼以下:

package netty.in.action;  
import io.netty.buffer.ByteBuf;  
import io.netty.buffer.Unpooled;  
import io.netty.channel.embedded.EmbeddedChannel;  
import io.netty.handler.codec.TooLongFrameException;  
import org.junit.Assert;  
import org.junit.Test;  
public class FrameChunkDecoderTest {  
    @Test  
    public void testFramesDecoded() {  
        //建立ByteBuf並填充9字節數據  
        ByteBuf buf = Unpooled.buffer();  
        for (int i = 0; i < 9; i++) {  
            buf.writeByte(i);  
        }  
        //複製一個ByteBuf  
        ByteBuf input = buf.duplicate();  
        //建立EmbeddedChannel  
        EmbeddedChannel channel = new EmbeddedChannel(new FrameChunkDecoder(3));  
        //讀取2個字節寫入入站通道  
        Assert.assertTrue(channel.writeInbound(input.readBytes(2)));  
        try {  
            //讀取4個字節寫入入站通道  
            channel.writeInbound(input.readBytes(4));  
            Assert.fail();  
        } catch (TooLongFrameException e) {  
              
        }  
        //讀取3個字節寫入入站通道  
        Assert.assertTrue(channel.writeInbound(input.readBytes(3)));  
        //標識完成  
        Assert.assertTrue(channel.finish());  
        //從EmbeddedChannel入去入站數據  
        Assert.assertEquals(buf.readBytes(2), channel.readInbound());  
        Assert.assertEquals(buf.skipBytes(4).readBytes(3),  
                channel.readInbound());  
    }  
}
相關文章
相關標籤/搜索