2011-04-07 15 views
26

Estoy desarrollando una aplicación de red y quiero hacer las pruebas unitarias correctamente. ESTA vez lo haremos, ¿sabes? :)Probando Java Sockets

Tengo problemas para probar las conexiones de red, sin embargo.

En mi aplicación utilizo plain java.net.Socket s.

Por ejemplo:

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; 
    } 
} 

leí sobre burlarse, pero no estoy seguro de cómo aplicarlo.

+3

En lugar de burlarse de un zócalo es mucho más simple en mi humilde opinión para crear un ServerSocket real (toma dos líneas) –

Respuesta

47

Si tuviera que probar el código, yo haría lo siguiente.

En primer lugar, refactorice el código para que el Socket no esté directamente instanciado en el método que desea probar. El siguiente ejemplo muestra el cambio más pequeño que puedo pensar para que eso suceda. Los cambios futuros pueden tener en cuenta la creación de Socket en una clase completamente separada, pero me gustan los pasos pequeños y no me gusta hacer grandes cambios en el código no probado.

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

Ahora que la lógica de la creación toma se encuentra fuera del método que está tratando de probar, puede comenzar a burlarse de las cosas y el gancho en la creación del zócalo.

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

métodos individuales imperativos de unidades que desea probar es realmente útil para las pruebas, especialmente en código feo, con dependencias horribles. Obviamente, la mejor solución es resolver dependencias (en este caso, creo que Message no depende de Socket, tal vez haya una interfaz Messager como lo sugiere el glowcoder), pero es bueno avanzar hacia la solución en los pasos más pequeños posibles.

+1

muchas gracias, esto es exactamente lo que estaba buscando –

+1

Excelente enfoque! Gracias. – Serge

+0

Tengo una excepción 'java.lang.VerifyError' cuando ejecuto' mock (Socket.class) ', ¿sabe por qué? –

-1

Es difícil probar la conexión y las interacciones del servidor.

Típicamente, aíslo la lógica de negocios de la lógica de comunicación.

Creo escenarios para pruebas unitarias en lógica comercial, estas pruebas son automáticas (use JUni, t y Maven) y creo otros escenarios para probar conexiones reales, no utilizo framework como JUnit para estas pruebas.

En el año pasado utilicé Spring Mocks para probar la lógica con HttpResponse HttpRequest, pero creo que esto no es útil.

Sigo esta pregunta.

adiós

+2

También trato de separar la lógica de negocios y comunicación, pero también quiero probar mi código de comunicación. He encontrado la solución de Jeff Foster muy útil. –

2

No voy a decir que esto es una mala idea.

Voy a decir que se puede mejorar.

Si está enviando un byte sin formato [] sobre su socket, el otro lado puede hacer lo que quiera con él. Ahora, si no se está conectando a un servidor Java, entonces NECESITARÍA hacer eso. Si está dispuesto a decir "siempre estoy trabajando con un servidor Java", puede usar la serialización para su beneficio.

Cuando hace esto, puede simularlo creando simplemente sus propios objetos Sendable como si se toparan con el cable.

Crea un socket, no uno para cada mensaje.

interface Sendable { 

    void callback(Engine engine); 

} 

Entonces, ¿cómo funciona esto en la práctica?

clase Messenger: clase

/** 
* 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); 
    } 

} 

receptor:

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

gracias por su respuesta, pero de hecho voy a interactuar con servidores y clientes que no son de Java, por lo que la serialización no es una opción para mí. –

+2

Si bien es probable que intentes probar el código existente, alguien más como yo puede estar investigando para un nuevo proyecto. Para ese tipo, hay una serialización independiente de la plataforma. Consulte https://github.com/google/protobuf/ – kd8azz

+0

O simplemente JSON para ese asunto. – Piovezan

10

Voy a responder a su pregunta como se le pidió en lugar de rediseñar su clase (otros lo tienen cubierto, pero la pregunta básica sobre la clase tal como está escrita es aún válida).

La prueba de unidades nunca prueba nada fuera de la clase que se está probando. Esto lastimó mi cerebro por un tiempo, ¡significa que la prueba unitaria no prueba de ninguna manera que tu código funcione! Lo que hace es probar que su código funciona de la misma manera que cuando escribió la prueba.

Eso dice que quiere una prueba de unidad para esta clase pero también quiere una prueba funcional.

Para la prueba unitaria, debe poder "simular" las comunicaciones. Para hacer esto en lugar de crear su propio socket, busque uno de una "fábrica de sockets", luego hágase una fábrica de sockets. La fábrica debe pasar al constructor de esta clase que está probando. En realidad, esta no es una mala estrategia de diseño: puede configurar el nombre de host y el puerto en la fábrica para que no tenga que saber sobre ellos en su clase de comunicación: más abstracto.

Ahora en las pruebas que acaba de pasar en una fábrica de simulación que crea tomas simuladas y todo es rosas.

¡No olvide la prueba funcional! Configure un "servidor de prueba" al que pueda conectarse, envíe algunos mensajes al servidor y pruebe las respuestas que recibe.

De hecho, es probable que quiera hacer pruebas funcionales aún más profundas en las que escriba un cliente que envíe al servidor REAL algunos comandos con guiones y pruebe los resultados. Probablemente incluso desee crear un comando "Restablecer estado" solo para pruebas funcionales. Las pruebas funcionales realmente aseguran que "Unidades funcionales" completas funcionen juntas como usted espera, algo que muchos defensores de las pruebas unitarias olvidan.