markk: Mocken eines ServerSockets und clients

Moin,

ich benötige einen ServerSocket-Mock und einen Client, der in definierten Abständen Daten an das Socket liefert.

Ich habe bereits etliche Beispiele, die ich finden konnte, ausprobiert. Leider ohne Erfolg.

Den Code habe ich bereits in so weit vereinfacht, dass es nur noch eine einfache Main-Class ist.

Mein Ziel

Ich möchte, dass ein Service lediglich MySocketMock.start() aufrufen muss, um einen Mock-Socket zu starten. Diese Klasse/Methdoe soll dazu einen Fake-Client initialisieren, der in einem separaten Thread kontinuierlich Daten an das Socket sendet.

Meine bisherigen Erfolge

  1. Der Server startet fein, blockiert aber den Verlauf der weiteren Anwendung
  2. Der Server failed während er versucht den Socket zu accepten (wahrscheinlich #1)
  3. Der Client wird lediglich einmal aufgerufen, selbst wenn ScheduledExecutorService genutzt wird
  4. Der Client führt gar keine Action aus
  5. Das Gesamtszenario funktioniert, aber eine CPU ist mit 100% ausgelastet (Bsp #2)

Bsp #1

`

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {

public static void main(String[] args) {
    new SocketMock(9988, 2).startSocket();
}

private static class SocketMock {

    private ServerSocket serverSocket;
    private ScheduledExecutorService executor;

    private int port;
    private int interval; // seconds;

    SocketMock(int port, int interval) {
    this.port = port;
    this.interval = interval;
    this.executor = Executors.newScheduledThreadPool(15);
    }

    void startSocket() {
    System.out.println("Starting socket mock");

    final Socket[] clientSocket = new Socket[1];

    Thread serverRunnable = new Thread(() -> {
        try {
        serverSocket = new ServerSocket(port);

        //while (true) {
        System.out.println("Accepting clients..");
        clientSocket[0] = serverSocket.accept();
        System.out.println("Clients accepted.");
        //}
        } catch (IOException e) {
        e.printStackTrace();
        }
        System.out.println("Mock server started");
    });

    // Attach Client
    executor.scheduleAtFixedRate(
        new ClientRunnable(clientSocket[0]),
        1,
        this.interval,
        TimeUnit.SECONDS
    );

    // Start Server
    Thread serverThread = new Thread(serverRunnable);
    serverThread.start();
    }

    private class ClientRunnable implements Runnable {
    Socket clientSocket;

    ClientRunnable(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        System.out.println("Got a client!");
        try {
        OutputStream outputStream = clientSocket.getOutputStream();
        PrintWriter out = new PrintWriter(outputStream);
        out.println("Yeah");
        out.flush();
        } catch (IOException e) {
        e.printStackTrace();
        }
    }
    }
}

}

`

Bsp #2

`

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ElevatorSocketMock {

    private int port;

    public ElevatorSocketMock(int port, int interval) {
        this.port = port;
        this.startServer(interval);
    }

    private void startServer(int interval) {
        final ExecutorService clientProcessingPool = Executors.newFixedThreadPool(10);

        Runnable serverTask = () -> {
        try {
            ServerSocket serverSocket = new ServerSocket(this.port);
            Logger.log(LogComponent.SOCKET, "Mock server started");
            while (true) {
            Socket clientSocket = serverSocket.accept();
            clientProcessingPool.submit(new ClientTask(clientSocket, interval));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        };

        Thread serverThread = new Thread(serverTask);
        serverThread.start();
    }

    private class ClientTask implements Runnable {

        private final Socket clientSocket;
        private final Integer interval;

        private Random random = new Random();

        private ClientTask(Socket clientSocket, int interval) {
        this.clientSocket = clientSocket;
        this.interval = interval;
        }

        @Override
        public void run() {
        Logger.log(LogComponent.SOCKET, "Got a client!");
        Runnable sendDummyEvent = () -> {

            String eventItem = this.generateRandomEvent();
            String event = this.wrapAsValidEvent(eventItem);

            try {
            OutputStream outputStream = this.clientSocket.getOutputStream();
            PrintWriter out = new PrintWriter(outputStream);
            out.println(event);
            out.flush();
            } catch (IOException e) {
            e.printStackTrace();
            }
        };

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(sendDummyEvent, 1, this.interval, TimeUnit.SECONDS);
        }

        private String wrapAsValidEvent(String eventItem) {
        return eventItem; // CODE CUT
        }

        private String generateRandomEvent() {
        return "foo";  // CODE CUT 
        }

    }

}`

Cheers, markk

  1. Hallo

    Wenns um Mocks geht, warum sehe ich keine Unit-Test-typischen Mechanismen, wie Methoden mit @setUp und @tearDown Annotations? Benutz Unit-Tests, IDEs haben dafür eine Integration, dass du nich so viel manuell machen musst.

    Deine "Erfolge" und deinen Code kann ich nicht wirklich in Zusammenhang bringen. Willst du einen Mock für den Server oder für den Client, deine Erfolge sprechen, wenn ich es nicht falsch verstehe unterschiedliche Seiten der Verbindung an.

    1. Der Server startet fein, blockiert aber den Verlauf der weiteren Anwendung

    Das ist unzureichend formuliert. Wird der Server im eigenen Thread laufen oder wird das eine asynchrone Sache über java.nio?

    1. Der Server failed während er versucht den Socket zu accepten (wahrscheinlich #1)

    Ein "fail" bei serverSocket.accept() wirft in jeden Fall eine Exception. Das ist auch schon ausreichend durch die JVM-Entwickler getestet wird.

    1. Der Client wird lediglich einmal aufgerufen, selbst wenn ScheduledExecutorService genutzt wird

    Okay, hier weiß ich überhaupt nicht, was du testen willst. Den Test selbst?

    1. Der Client führt gar keine Action aus

    Definiere "Action"! Allgemein sollte, wenn der Server keine Reaktion vom Client bekommt (aber das ist anwendungsabhängig!) nach einer bestimmten Zeit einfach die Verbindung trennen, eventuell vorher noch eine Nachricht schicken, falls der Client doch noch valid dabei ist, dass er auf die serverseitige Trennung entsprechend reagieren kann.

    1. Das Gesamtszenario funktioniert, aber eine CPU ist mit 100% ausgelastet (Bsp #2)

    Kann ich an Bsp #2 nicht nachvollziehen, da sowieso nicht gezeigt wird, wie was initialisiert/ausgeführt wird.

    Kurz und knapp: Das ist alles sehr durcheinander, formuliere einfache und kurze - dafür aber wohl viele Tests. Damit kannst du schneller und einfacher gucken, was wo hängt. (Damit wäre dein Ziel einfach nur alle Tests über einen Aufruf durchzuführen, das geht mit Unit-Tests halt sehr schnell)

    Da du keine Frage gestellt hast, hoffe ich dass diese Antwort reicht, wenn nicht, probier es mal mit expliziten Fragen.

    Grüße