2016-12-16 5 views
0

私は、ソケットチャネルでselectを実行するJavaクラスを持っています。私は、選択したワークを期待通りにテストできるように、チャネルをスタブしたいと思います。SpockでSocketChannelをスタブまたはモックすることは可能ですか?

例えば、これはテストされているクラスが何をするかおおよそ次のとおりです。

class TestedClass { 
    TestedClass(SocketChannel socket) { this.socket = socket } 

    // ... 
    SocketChannel socket; 
    // ... 
    void foo() { 
     // Wait a while for far end to close as it will report an error 
     // if we do it. But don't wait forever! 
     // A -1 read means the socket was closed by the other end. 
     // If we select on a read, we can check that, or time out 
     // after a reasonable delay. 

     Selector selector = Selector.open(); 
     socket.configureBlocking(false); 
     socket.register(selector, SelectionKey.OP_READ); 
     while(selector.select(1000) == 0) { 
      Log.debug("waiting for far end to close socket...") 
     } 

     ByteBuffer buffer = ByteBuffer.allocate(1); 
     if (socket.read(buffer) >= 0) { 
      Log.debug("far end didn't close"); 
      // The far end didn't close the connection, as we hoped 
      abort(AbortCause.ServerClosed); 
     } 

     Log.debug("far end closed"); 
    } 
} 

私はこのような何かをテストできるようにしたいと思います。実際の例をもとに

def "test we don't shut prematurely"() { 
    when: 
    boolean wasClosedPrematurely 
    SocketChannel socket = Stub(@SocketChannel) { 
     // insert stub methods here .... 
    } 

    TestedClass tc = new TestedClass(socket) 
    tc.foo(); 

    then: 
    wasClosedPrematurely == false 
} 

この詳細は重要ではありません。一般的な目的は、selectをサポートするSocketChannelsをスタブする方法です。テストするために実際のクライアントを作成する必要はありません。

私はまた、SocketChannelをスタブするより複雑です:私はSelector.open()を傍受するか、何らかの形でカスタムシステムのデフォルトSelectorProviderを提供する必要があるようです。私が単純にSocketChannelをスタブした場合、Selection.open()で取得したセレクタをスタブに登録しようとするとIllegalSelectorExceptionが発生し、ベースのAbstractSelectableChannel#registerメソッドは残念ながら最後です。

しかし、これがSpock Mockでどのように可能かどうか、またSpock Mockで可能かどうかは分かりません。よく尋ねるとよい質問です。誰も助けることができますか?

答えて

1

SpockはCGLIBを使って模擬/スタブ/スパイのクラスを使用します。 CGLIBは最終的なメソッドをオーバーライドできません。 SocketChannelには多くの最終的なメソッド(configureBlockingなど)がありますが、CGLIBは失敗せず独自のメソッドを使用します。 configureBlockingは最終的なので、テストで使用されます。

public final SelectableChannel configureBlocking(boolean block) throws IOException { synchronized (regLock) { if (!isOpen()) throw new ClosedChannelException(); if (blocking == block) return this; if (block && haveValidKeys()) throw new IllegalBlockingModeException(); implConfigureBlocking(block); blocking = block; } return this; }

のでconfigureBlockingはregLock変数が初期化された必要ですが、あなたはこのクラスのスタブを作るよう変数が初期化され、あなたがここにNPEを取得していません。

質問はどうすればよいですか? さて、私はまず、インタフェースではなくクラスを使用しようとします。 可能でない場合は、最終メソッドを呼び出さないようにしてください。 まだ不可能な場合は、クラスを調べて、何を嘲笑すべきかを把握する必要があります。 私が最後に見るオプションは、完全な統合テストを行うことです:2つのソケットを折り畳み、それらを接続することです。

+0

スタブのSocketChannel {新しい例外()のprintStackTrace()}' configureBlockingとimplConfigureBlocking両方が呼び出される示します。なんらかの理由で、後者のスタブメソッドを追加することができません。しかし、これはショーストッパーではなく、#registerメソッドが呼び出されたときです。それは最終的なものであり、ソケットプロバイダが内部プライベートインターフェイスを実装していることを内部(Java 7)がチェックし、IllegalSelectorExceptionをスローします。 –

+0

問題はSelectorImpl :: register、#117行にあると思います。 if(!(varI instanceof SelChImpl)){ 新しいIllegalSelectorException()をスローします。 } else {... 正に、抽象クラス(SocketChannel)のインスタンスを持つことはできませんが、CGLIBには抽象クラスのインスタンスがあります。 SocketChannelの代わりにSocketChannelを汚れたハックとして使用しようとしますが、テスト全体を見直し、タイプ(ユニット、受け入れ、統合)を考え、抽象クラスをスタブするようなハックやトリックを使わずに書き直します。 –

+0

正確には、SelChImplインスタンスを取得するために内部実装全体を使用するか、何らかの形でこのチェックを行わない自分自身のものを返すために 'Selection.open()'を誘導する必要があります。私は、後者の解決策のためにいくつかの先行技術があるかもしれないことを望んでいました。これは、相互接続された種類の束を再実装する必要があるように思われるからです。いずれにせよ、NIOの設計は簡単な単体テストを妨げるようです!そして、ウェブ上のコメントはほとんどありません。 –

0

私自身の質問に対する答えを発見したかもしれないと思います。

のでSelector.open()は直接傍受することはできません - それは、単にSocketProvider.provider().openSelector()を呼び出し、SocketProvider.provider()SocketProvider.providerフィールドの怠惰な静的なアクセサです。 (私の場合、少なくとも私の場合、Java 7)

したがって、Groovyは通常のJavaの可視性制限を無視できるため、providerフィールドはprivateであっても簡単に設定できます。私たち自身のスタブインスタンスに設定されると、将来のすべてのSelector.open()呼び出しはそれを使用します(これはテスト中の他のコードに影響を与える可能性のあるグローバルな変更であるという明白な注意が必要です)。

詳細は、あなたが何をしたいかによって異なりますが、以下のように、AbstractSelectableChannelなどの他のクラスのスタブを返すことができます。

実例を以下に示します。ワイルドカード方法 `_ >>と

class SocketStubSpec extends Specification { 

    SocketChannel makeSocketChannel(List events) { 
     // Insert our stub SelectorProvider which stubs everything else 
     // required, and records what happened in the events list. 
     SelectorProvider.provider = Stub(SelectorProvider) { 
      openSelector() >> { 
       Map<SelectionKey, AbstractSelectableChannel> keys = [:] 

       return Stub(AbstractSelector) { 
        register(_,_,_) >> { AbstractSelectableChannel c, int ops, Object att -> 
         events << "register($c, $ops, $att)" 
         SelectionKey key = Stub(SelectionKey) { 
          readyOps() >> { events << "readyOps()"; ops } 
          _ >> { throw new Exception() } 
         } 
         keys[key] = c 
         return key 
        } 
        select() >> { 
         events << "select()" 
         return keys.size() 
        } 
        selectedKeys() >> { keys.keySet() } 
        _ >> { throw new Exception() } 
       } 
      } 
      _ >> { throw new Exception() } 
     } 

     return Stub(SocketChannel) { 
      implConfigureBlocking(_ as Boolean) >> { boolean state -> events << "implConfigureBlocking($state)" } 
      _ >> { throw new Exception() } 
     } 
    } 

    def "example of SocketChannel stub with Selector"() { 
     given: 
     List events = [] 

     // Create a stub socket 
     SocketChannel channel = makeSocketChannel(events) 

     Selector selector = Selector.open() 
     channel.configureBlocking(false); 
     SelectionKey key = channel.register(selector, SelectionKey.OP_READ); 

     expect: 
     selector.select() == 1 // our implementation doesn't block 
     List keys = selector.selectedKeys().asList() 

     keys == [key] // we have the right key 
     key.isReadable() // key is readable, etc. 

     // Things happened in the right order 
     events == [ 
      "implConfigureBlocking(false)", 
      "register(Mock for type 'SocketChannel', 1, null)", 
      "select()", 
      "readyOps()", 
     ] 
    } 
} 
関連する問題