Тестирование сокетов Java
Я занимаюсь разработкой сетевого приложения и хочу правильно провести модульное тестирование. В этот раз мы сделаем это, вы знаете?:)
У меня проблемы с тестированием сетевых подключений.
В моем приложении я использую обычный 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;
}
}
Я читал о насмешках, но не уверен, как их применять.
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, но я думаю, что это бесполезно.
Я следую за этим вопросом.
до свидания