web-dev-qa-db-fra.com

Test Java Sockets

Je développe une application réseau et je souhaite que les tests unitaires soient corrects. Cette fois, nous le ferons, vous savez? :)

J'ai cependant du mal à tester les connexions réseau.

Dans mon application, j'utilise plain Java.net.Sockets.

Par exemple:

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

J'ai lu sur la moquerie mais je ne sais pas comment l'appliquer.

29
Zoran Zaric

Si je devais tester le code, je ferais ce qui suit.

Tout d'abord, refactorisez le code afin que le Socket ne soit pas directement instancié dans la méthode que vous souhaitez tester. L'exemple ci-dessous montre le plus petit changement auquel je puisse penser pour y arriver. Les modifications futures pourraient prendre en compte la création de Socket dans une classe complètement distincte, mais j'aime les petites étapes et je n'aime pas apporter de grandes modifications au code non testé.

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

Maintenant que la logique de création de socket est en dehors de la méthode que vous essayez de tester, vous pouvez commencer à simuler et à accrocher la création du 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());
    }
}

Remplacer les méthodes individuelles sur les unités que vous souhaitez tester est vraiment utile pour les tests, en particulier dans le code laid avec des dépendances horribles. Évidemment, la meilleure solution consiste à trier les dépendances (dans ce cas, je pense qu'un Message ne dépend pas d'un Socket, peut-être qu'il y a une interface Messager comme le suggère glowcoder), mais c'est bien d'avancer vers la solution dans les plus petites étapes possibles.

52
Jeff Foster

Je vais répondre à votre question comme demandé au lieu de repenser votre classe (d'autres l'ont couverte, mais la question de base sur la classe telle qu'elle est écrite est toujours valable).

Les tests unitaires ne testent jamais rien en dehors de la classe testée. Cela m'a fait mal au cerveau pendant un certain temps - cela signifie que le test unitaire ne prouve en aucune façon que votre code fonctionne! Ce qu'il fait, c'est prouver que votre code fonctionne de la même manière que lorsque vous avez écrit le test.

Cela dit, vous voulez un test unitaire pour cette classe mais vous voulez aussi un test fonctionnel.

Pour le test unitaire, vous devez pouvoir "simuler" les communications. Pour ce faire au lieu de créer votre propre socket, récupérez-en un dans une "fabrique de socket", puis créez-vous une fabrique de socket. L'usine doit être transmise au constructeur de cette classe que vous testez. Ce n'est en fait pas une mauvaise stratégie de conception - vous pouvez définir le nom d'hôte et le port en usine afin que vous n'ayez pas à les connaître dans votre classe de communication - plus abstrait.

Maintenant, lors des tests, vous passez simplement dans une usine fictive qui crée de fausses prises et tout est rose.

N'oubliez pas le test fonctionnel! Configurez un "serveur de test" auquel vous pouvez vous connecter, envoyez des messages au serveur et testez les réponses que vous obtenez.

D'ailleurs, vous voudrez probablement faire des tests fonctionnels encore plus approfondis où vous écrivez un client qui envoie au serveur REAL des commandes scriptées et teste les résultats. Vous voudrez probablement même créer une commande "Réinitialiser l'état" juste pour les tests fonctionnels. Les tests fonctionnels garantissent en fait que des "unités fonctionnelles" entières fonctionnent ensemble comme vous vous y attendez - ce que de nombreux défenseurs des tests unitaires oublient.

11
Bill K

Je ne vais pas dire que c'est une mauvaise idée.

Je vais dire que cela peut être amélioré.

Si vous envoyez un octet brut [] sur votre socket, l'autre côté peut en faire ce qu'il veut. Maintenant, si vous ne vous connectez pas à un serveur Java, alors vous pourriez avoir BESOIN de le faire. Si vous êtes prêt à dire "je travaille toujours avec un Java server ", vous pouvez alors utiliser la sérialisation à votre avantage.

Lorsque vous faites cela, vous pouvez vous en moquer en créant simplement vos propres objets Sendable comme s'ils étaient tombés sur le fil.

Créez un socket, pas un pour chaque message.

interface Sendable {

    void callback(Engine engine);

}

Alors, comment cela fonctionne-t-il dans la pratique?

Classe Messenger:

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

}

Classe de récepteur:

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
        }
    }
}
2
corsiKa