开发者

Testing Java Sockets

I'm developing a network application and I want to get unit testing right. THIS time we'll do it, you know? :)

I'm have trouble testing network connections, though.

In my application I use plain java.net.Sockets.

For example:

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class Message {
  开发者_Python百科  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;
    }
}

I read about mocking but am not sure how to apply it.


If I was to test the code, I'd do the following.

Firstly, refactor the code so that the Socket isn't directly instantiated in the method you want to test. The example below shows the smallest change I can think of to make that happen. Future changes might factor out the Socket creation to a completely separate class, but I like small steps and I don't like making big changes on untested code.

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

Now that the socket creation logic is outside of the method you are trying to test, you can start to mock things up and hook into the creation the 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());
    }
}

Overriding individual methods on units you want to test is really useful for testing, especially in ugly code with horrible dependencies. Obviously the best solution is sorting out dependencies (in this case I would think that a Message not depend on a Socket, maybe there is a Messager interface as glowcoder suggests), but it's nice to move towards the solution in the smallest possible steps.


I'm going to answer your question as asked instead of redesigning your class (others have that covered, but the basic question on the class as written is stil valid).

Unit testing never tests anything outside the class being tested. This hurt my brain for a while--it means unit test does not in any way prove that your code works! What it does is prove that your code works the same way it did when you wrote the test.

So that said you want a unit test for this class but you also want a functional test.

For the unit test you have to be able to "Mock out" the communications. To do this instead of creating your own socket, fetch one from a "Socket factory", then make yourself a socket factory. The factory should be passed in to the constructor of this class you are testing. This is actually not a bad design strategy--you can set the hostname and port in the factory so you don't have to know about them in your communication class--more abstract.

Now in testing you just pass in a mock factory that creates mock sockets and everything is roses.

Don't forget the functional test though! Set up a "test server" that you can connect to, send some messages to the server and test the responses you get back.

For that matter, you probably want to do even deeper functional tests where you write a client that sends the REAL server some scripted commands and tests the results. You probably even want to create a "Reset state" command just for functional testing. Functional tests actually ensure that entire "Functional units" work together as you expect--something that many unit testing advocates forget.


I'm not going to say this is a bad idea.

I am going to say it can be improved upon.

If you're sending across a raw byte[] over your socket, the other side can do whatever it wants with it. Now, if you're not connecting to a Java server, then you might NEED to do that. If you're willing to say "I'm always working with a Java server" then you can use serialization to your advantage.

When you do this, you can mock it by just creating your own Sendable objects as if they came across the wire.

Create one socket, not one for every message.

interface Sendable {

    void callback(Engine engine);

}

So how does this work in practice?

Messenger class:

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

}

Receiver class:

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


It's difficult testing connection and server interactions.

Tipically, I isolate business logic from communication logic.

I create scenarios for unit tests on business logic, these tests are automatic (use JUni,t and Maven) and i create other scenarios to test real connections, I don't use framework like JUnit for these tests.

In last year I've used Spring Mocks to test logic with HttpResponse HttpRequest, but I think this is not useful.

I follow this question.

Bye

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜