2016-12-16 17 views
0

본질적으로 소켓 채널에서 select를 수행하는 Java 클래스가 있으며 예상대로 select 작업을 테스트 할 수 있도록 채널을 스텁하고자합니다.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를 스텁 (stub)하는 방법입니다. 따라서 테스트 할 실제 클라이언트를 만들 필요가 없습니다.

또한 SocketChannel을 스터 빙하는 것보다 더 복잡하다는 것을 알고 있습니다. Selector.open()을 가로 채거나 사용자 정의 시스템 기본 SelectorProvider를 제공해야하는 것처럼 보입니다. 간단히 SocketChannel을 스텁하면 Selection.open()을 통해 얻은 셀렉터를 내 스텁에 등록하려고하면 IllegalSelectorException이 발생하고 AbstractSelectableChannel#register 메서드는 최종적으로 최종 메서드입니다.

하지만 Spock Mocks가 어떻게 가능한지 또는 유용한 지에 대한 유용한 정보를 찾을 수 없으므로 여기에 묻기 좋은 질문이 될 것 같습니다. 누구든지 도와 줄 수 있습니까?

답변

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를 얻을.

질문은 무엇과 관련이 있습니까? 글쎄, 우선, 인터페이스를 사용하려고하지만 클래스는 사용하지 말아야한다. 가능하지 않은 경우 최종 메소드를 호출하지 마십시오. 아직도 불가능하다면 수업을 들여다보고 조롱 거리를 찾아야합니다. 마지막으로 볼 수있는 것은 통합 테스트를 만드는 것입니다. 두 개의 소켓을 creaeting하고 연결하는 것입니다.

+0

스텁에 SocketChannel {새로운 예외()의 printStackTrace().}'configureBlocking 및 implConfigureBlocking 모두 호출되는 프로그램; 어떤 이유 때문에 후자의 스텁 메서드를 추가하는 것이 저에게 효과적이지 않습니다. 하지만 이것은 쇼 스토퍼가 아닙니다. #register 메소드가 호출 될 때입니다. 그것도 최종적이며 내부 (Java 7)는 소켓 공급자가 내부 개인 인터페이스를 구현하는지 확인하고, 그렇지 않은 경우 IllegalSelectorException을 던집니다. –

+0

문제는 SelectorImpl :: register, # 117 번 줄에 있다고 생각합니다. if (! (var1 instanceof SelChImpl)) { 새로운 IllegalSelectorException()을 throw합니다. } else {... 솔직히 말해, 추상 클래스 (SocketChannel)의 인스턴스를 가질 수는 없지만 CGLIB에서는이를 가질 수 있습니다. 더러운 해킹으로 SocketChannel 대신 SocketChannelImpl을 사용하려고 시도하지만 전체 테스트를 검토하고 유형 (유닛, 수락, 통합)을 생각하고 추상 클래스를 스텁하는 등의 해킹 및 트릭을 사용하지 않고 다시 작성합니다. –

+0

정확하게 : SelChImpl 인스턴스를 얻으려면 내부 구현을 모두 사용해야합니다. 그렇지 않으면이 검사를 부과하지 않는 자체 결과 중 하나를 반환하도록 'Selection.open()'을 유도해야합니다. 후자의 솔루션에 대한 선행 기술이 있기를 바랬습니다. 상호 연결된 유형을 다시 구현해야하는 것 같습니다. 어느 쪽이든, NIO의 디자인은 쉬운 단위 테스트를 방해하는 것 같습니다! 웹에 대한 언급은 거의 없습니다. –

0

나는 내 자신의 질문에 대한 답을 발견했을 것 같습니다.

은 그래서 Selector.open() 직접 가로 챌 수 없습니다 -하지만 단순히 SocketProvider.provider().openSelector()를 호출하고 SocketProvider.provider()SocketProvider.provider 필드 게으른 정적 접근이다. (최소한 내 경우 Java 7)

그러므로 provider 필드는 개인용이지만 Groovy가 일반적인 Java 가시성 제한 사항을 무시할 수 있기 때문에 간단하게 설정할 수 있습니다. 우리 자신의 스텁 인스턴스로 설정되면 이후의 모든 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()", 
     ] 
    } 
}