2011-04-07 7 views
26

네트워크 응용 프로그램을 개발 중이며 단위 테스트를 제대로 수행하고 싶습니다. 이 시간에 우리가 할거야, 알지? :)Java 소켓 테스트

그래도 네트워크 연결 테스트에 문제가 있습니다.

내 응용 프로그램에서는 일반 java.net.Socket을 사용합니다. 예를 들어

는 :

import java.io.IOException; 
import java.io.OutputStream; 
import java.net.Socket; 
import java.net.UnknownHostException; 

public class Message { 
    byte[] payload; 

    public Message(byte[] payload) { 
     this.payload = payload; 
    } 

    public boolean sendTo(String hostname, int port) { 
     boolean sent = false; 

     try { 
      Socket socket = new Socket(hostname, port); 

      OutputStream out = socket.getOutputStream(); 

      out.write(payload); 

      socket.close(); 

      sent = true; 
     } catch (UnknownHostException e) { 
     } catch (IOException e) { 
     } 

     return sent; 
    } 
} 

나는 조롱에 대해 읽을 수 있지만 그것을 적용하는 방법을 잘 모르겠습니다.

+3

소켓을 조롱하는 대신 실제 ServerSocket (2 줄 사용)을 작성하는 데 훨씬 간단한 IMHO입니다. –

답변

47

, 나는 다음을 수행 할 것입니다.

먼저 코드를 리팩토링하여 Socket이 테스트하려는 메서드에서 직접 인스턴스화되지 않도록하십시오. 아래의 예는 그렇게 할 수있는 가장 작은 변화를 보여줍니다. 나중에 변경하면 Socket 생성을 완전히 분리 된 클래스로 분해 할 수 있지만 작은 단계가 마음에 들지 않으므로 테스트되지 않은 코드를 크게 변경하는 것은 싫어합니다.

public boolean sendTo(String hostname, int port) { 
    boolean sent = false; 

    try { 
     Socket socket = createSocket(); 
     OutputStream out = socket.getOutputStream(); 
     out.write(payload); 
     socket.close(); 
     sent = true; 
    } catch (UnknownHostException e) { 
     // TODO 
    } catch (IOException e) { 
     // TODO 
    } 

    return sent; 
} 

protected Socket createSocket() { 
    return new Socket(); 
} 

지금 소켓 생성 로직 테스트하려고하는 방법 이외의 것을, 당신은 가짜 일까지로 시작하고 생성에 소켓을 연결할 수 있습니다.

public class MessageTest { 
    @Test 
    public void testSimplePayload()() { 
     byte[] emptyPayload = new byte[1001]; 

     // Using Mockito 
     final Socket socket = mock(Socket.class); 
     final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
     when(socket.getOutputStream()).thenReturn(byteArrayOutputStream); 

     Message text = new Message(emptyPayload) { 
      @Override 
      protected Socket createSocket() { 
       return socket; 
      } 
     }; 

     Assert.assertTrue("Message sent successfully", text.sendTo("localhost", "1234")); 
     Assert.assertEquals("whatever you wanted to send".getBytes(), byteArrayOutputStream.toByteArray()); 
    } 
} 

테스트 할 단위의 개별 메소드를 무시하는 것은 테스트에 특히 유용합니다. 특히 끔찍한 의존성이있는 코드에서는 테스트가 유용합니다. 분명히 가장 좋은 해결책은 의존성을 분류하는 것입니다 (이 경우에는 Socket에 의존하지 않고인터페이스가 glowcoder에서 제안하는 것처럼 보일 것입니다). 그러나 가능한 한 가장 작은 단계에서 솔루션쪽으로 이동하는 것이 좋습니다.

+1

많이 고마워,이게 정확히 내가 무엇을 찾고 있었는지 –

+1

훌륭한 접근! 감사. – Serge

+0

'mock (Socket.class)'를 실행할 때'java.lang.VerifyError' 예외가 발생합니다. 왜 그런지 알고 있습니까? –

-1

연결 및 서버 상호 작용을 테스트하는 것은 어렵습니다.

요컨대, 나는 통신 논리에서 비즈니스 논리를 격리합니다.

비지니스 로직에 대한 유닛 테스트를위한 시나리오를 생성합니다.이 테스트는 자동 (JUni, t 및 Maven 사용)이며 실제 연결을 테스트하기 위해 다른 시나리오를 작성합니다. 이러한 테스트에는 JUnit과 같은 프레임 워크를 사용하지 않습니다.

작년에 HttpResponse HttpRequest를 사용하여 로직을 테스트하기 위해 Spring Mocks를 사용해 왔지만 유용하지는 않습니다.

나는이 질문을 따른다.

bye

+2

비즈니스 및 통신 논리를 분리하려고하지만 통신 코드를 테스트하고 싶습니다. Jeff Foster의 솔루션이 매우 유용하다는 것을 알았습니다. –

2

나는 이것이 나쁜 생각이라고 말하지 않을 것입니다.

나는 그것을 향상시킬 수 있다고 말할 것입니다.

소켓을 통해 원시 바이트 []를 보낼 경우 상대방은 원하는대로 할 수 있습니다. 이제 Java 서버에 연결하지 않은 경우 Java 서버에 연결해야합니다. "나는 항상 Java 서버로 작업하고있다"고 말하면 기꺼이 직렬화를 사용할 수 있습니다.

이렇게하면 전선을 지나친 것처럼 자신의 Sendable 개체를 만들어 조롱 할 수 있습니다.

모든 메시지마다 하나가 아닌 하나의 소켓을 만듭니다.

interface Sendable { 

    void callback(Engine engine); 

} 

실제로 어떻게 작동합니까?

메신저 클래스 :

/** 
* Class is not thread-safe. Synchronization left as exercise to the reader 
*/ 
class Messenger { // on the client side 

    Socket socket; 
    ObjectOutputStream out; 

    Messenger(String host, String port) { 
     socket = new Socket(host,port); 
     out = new ObjectOutputStream(socket.getOutputStream()); 
    } 

    void sendMessage(Sendable message) { 
     out.writeObject(out); 
    } 

} 

수신기 클래스 : 내가 코드를 테스트했다 경우

class Receiver extends Thread { // on the server side 

    Socket socket; 
    ObjectInputStream in; 
    Engine engine; // whatever does your logical data 

    Receiver(Socket socket, Engine engine) { // presumable from new Receiver(serverSocket.accept()); 
     this.socket = socket; 
     this.in = new ObjectInputStream(socket.getInputStream()); 
    } 

    @Override 
    public void run() { 
     while(true) { 
      // we know only Sendables ever come across the wire 
      Sendable message = in.readObject(); 
      message.callback(engine); // message class has behavior for the engine 
     } 
    } 
} 
+0

답장을 보내 주셔서 감사합니다.하지만 실제로는 비 자바 서버 및 클라이언트와 상호 작용할 것이므로 직렬화는 나를위한 옵션이 아닙니다. –

+2

기존 코드를 테스트하려고 할 가능성이 있지만 나 같은 사람이 새로운 프로젝트를 위해 연구를하고있을 수 있습니다. 그 사람에게는 플랫폼 독립적 인 직렬화가 있습니다. https://github.com/google/protobuf/ – kd8azz

+0

또는 간단한 JSON을 확인하십시오. – Piovezan

10

나는 수업을 재 설계하는 대신 질문에 답할 것입니다. (다른 사람들은 다루지 만, 수업의 기본 질문은 유효합니다.)

단위 테스트는 테스트중인 클래스 외부의 모든 것을 테스트하지 않습니다. 이것은 잠시 동안 내 머리를 아프게합니다 - 이것은 단위 테스트가 어떤 식 으로든 코드 작동을 증명하지 않는다는 것을 의미합니다! 코드가 테스트를 작성할 때와 같은 방식으로 작동한다는 것이 증명됩니다.

그래서이 클래스에 대한 단위 테스트를 원하지만 기능 테스트도 필요하다고합니다.

단위 테스트의 경우 통신을 "조롱 (mock out)"할 수 있어야합니다. 자신의 소켓을 생성하는 대신에 이것을 수행하려면 "소켓 팩토리"에서 소켓을 가져 와서 소켓 팩토리로 만드십시오. 팩토리는 테스트중인이 클래스의 생성자에 전달되어야합니다. 이것은 실제로 나쁜 설계 전략이 아닙니다. 팩토리에서 호스트 이름과 포트를 설정할 수 있으므로 통신 클래스에서 호스트 이름과 포트를 알 필요가 없으므로 더 추상적입니다.

이제 테스트에서 모의 ​​소켓을 만드는 모의 팩토리를 전달하면 모든 것이 장미가됩니다.

기능 테스트는 잊지 마세요. 연결할 수있는 "테스트 서버"를 설정하고 서버에 일부 메시지를 보내고 응답을 테스트하십시오.

그런 점에서 REAL 서버에 스크립트 명령을 보내고 결과를 테스트하는 클라이언트를 작성하는 경우 더 깊은 기능 테스트를 수행하는 것이 좋습니다. 아마도 기능 테스트를 위해서만 "재설정 상태"명령을 만들고 싶을 수도 있습니다. 기능 테스트는 실제로 "Functional units"전체가 예상대로 작동 함을 보장합니다. 이는 많은 단위 테스트 옹호자들이 잊어 버리는 것입니다.