Тестирование сокетов Java

Я занимаюсь разработкой сетевого приложения и хочу правильно провести модульное тестирование. В этот раз мы сделаем это, вы знаете?:)

У меня проблемы с тестированием сетевых подключений.

В моем приложении я использую обычный java.net.Sockets.

Например:

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

Я читал о насмешках, но не уверен, как их применять.

4 ответа

Решение

Если бы я должен был проверить код, я бы сделал следующее.

Во-первых, рефакторинг кода, чтобы 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());
    }
}

Переопределение отдельных методов для модулей, которые вы хотите протестировать, действительно полезно для тестирования, особенно в уродливом коде с ужасными зависимостями. Очевидно, что лучшим решением является сортировка зависимостей (в этом случае я думаю, что Message не зависит от Socketможет быть, есть Messager интерфейс, как предлагает свечение), но приятно идти к решению в наименьших возможных шагах.

Я собираюсь ответить на ваш вопрос в том виде, в котором он был задан, вместо того, чтобы перепроектировать ваш класс (у других это есть, но основной вопрос о классе в том виде, в котором он написан, остается в силе)

Модульное тестирование никогда не тестирует ничего, кроме тестируемого класса. Это навредило моему мозгу на некоторое время - это означает, что модульное тестирование никоим образом не доказывает, что ваш код работает! Это доказывает, что ваш код работает так же, как и при написании теста.

Итак, вы сказали, что вам нужен модульный тест для этого класса, но вы также хотите функциональный тест.

Для модульного теста вы должны быть в состоянии "Макетировать" связь. Чтобы сделать это вместо создания своего собственного сокета, извлеките его из "фабрики сокетов", а затем создайте фабрику сокетов. Фабрика должна быть передана конструктору этого класса, который вы тестируете. На самом деле это не плохая стратегия проектирования - вы можете установить имя хоста и порт на заводе, чтобы вам не нужно было знать о них в вашем классе связи - более абстрактно.

Теперь в тестировании вы просто проходите на фиктивной фабрике, которая создает фиктивные розетки и все это розы.

Не забудьте про функциональный тест! Настройте "тестовый сервер", к которому вы можете подключиться, отправьте несколько сообщений на сервер и протестируйте полученные ответы.

В этом отношении вы, вероятно, захотите провести еще более глубокие функциональные тесты, в которых вы пишете клиент, который отправляет на сервер REAL несколько скриптовых команд и проверяет результаты. Возможно, вы даже захотите создать команду "Сброс состояния" только для функционального тестирования. Функциональные тесты фактически гарантируют, что целые "функциональные блоки" работают вместе, как вы ожидаете, - то, что многие сторонники модульного тестирования забывают.

Я не собираюсь говорить, что это плохая идея.

Я собираюсь сказать, что это может быть улучшено.

Если вы отправляете необработанный байт [] через сокет, другая сторона может делать с ним все, что пожелает. Теперь, если вы не подключаетесь к серверу 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
        }
    }
}

Это сложное тестирование соединения и взаимодействия с сервером.

Как правило, я изолирую бизнес-логику от логики общения.

Я создаю сценарии для модульных тестов по бизнес-логике, эти тесты являются автоматическими (используйте JUni,t и Maven), и я создаю другие сценарии для тестирования реальных соединений, я не использую фреймворк, такой как JUnit, для этих тестов.

В прошлом году я использовал Spring Mocks для тестирования логики с HttpResponse HttpRequest, но я думаю, что это бесполезно.

Я следую за этим вопросом.

до свидания

Другие вопросы по тегам