Подтвердить что ты не робот

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

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

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

В моем приложении я использую plain java.net.Socket s.

Например:

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

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

Теперь в тестировании вы просто передаете макет factory, который создает ложные сокеты, и все это розы.

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

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

Ответ 3

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

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

Если вы отправляете по сырому байту [] поверх своего сокета, другая сторона может делать с ним все, что захочет. Теперь, если вы не подключаетесь к серверу Java, вам может понадобиться сделать это. Если вы хотите сказать "Я всегда работаю с сервером Java", вы можете использовать сериализацию в своих интересах.

Когда вы это сделаете, вы можете издеваться над этим, просто создав свои собственные объекты для отправки, как если бы они натолкнулись на провод.

Создайте один сокет, а не один для каждого сообщения.

interface Sendable {

    void callback(Engine engine);

}

Итак, как это работает на практике?

Класс 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(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
        }
    }
}

Ответ 4

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

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

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

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

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

Bye