Building Testable Network Programs
All code should come with tests that cover everything that the computer and user can do to break it, and they should check that it performs to the specification, or however the programmer felt that it should at the time1. This is generally possible (even if most unit tests test really simple uses, and many people don't even bother with the simple tests to write such as passing null as an argument, checking for the immutability of member objects, and so forth): for example, most website interactions (and file interactions) can be tested by including test files as part of the test suite. Interaction with an entire class of remote service, however, can be rather problematic in certain types of code.
The rest of this post is targeted at Java. A quick look at the internet tells me that there are libraries out there which can be used for testing socket interactions. I suspect some of them work on a similar method to what I'm about to outline, but this is one of those things that I did because I wanted to be sure I could do it myself. The idea was to make a method for testing socket interactions which was entirely machine and network independent, so that protocol testing could be performed.
At an abstract level, a socket is little more than a pair of streams going between two endpoints - each endpoint can send and receive. For most uses of sockets, once the connection is made, the only real use of the socket is it'd InputStream and OutputStream. So, the problem is really to offer a pair local (buffered) streams such that the input and outputs are available in the right pairs to the correct object.
First, building a local buffered stream. The code for this is simple enough, thanks to InputStream and OutputStream being easy to extend, and some help from the java.util.Concurrent package.
public class LocalBufferedSteam
{
private final BlockingQueue buffer = new LinkedBlockingQueue();
public final InputStream in = new InputStream()
{
public int read()
{
return buffer.take();
}
}
public final OutputStream out = new OutputStream()
{
public void write(int b)
{
q.put(b & 0xFF);
}
}
}
This code is a good starting place for this kind of implementation - it does, however, have some issues with flushing, and the contracts of some of the other functions can be fulfilled more efficiently with some additional code. A similar piece of code can be seen at a related project of mine on GitHub (it also has a number of tests! :D)
The socket itself is harder to build with such ease. However, as the methods are not final, entirely subclassing the Socket is a possibility - a rough example given here would probably throw a large number of exceptions if anything is ever touched but the streams.
public class LocalSocket
{
private final LocalBufferedSteam toRemote = new LocalBufferedSteam();
private final LocalBufferedSteam toLocal = new LocalBufferedSteam();
public final Socket localSocket = new Socket()
{
public InputStream getInputStream()
{
return toRemote.in;
}
public OutputStream getOutputStream()
{
return toLocal.out;
}
}
public final Socket remoteSocket = new Socket()
{
public InputStream getInputStream()
{
return toLocal.in;
}
public OutputStream getOutputStream()
{
return toRemote.out;
}
}
}
Now, I've put a huge number of bad practices into this. Public member variables, anonymous class definitions with essentially shared code, and incomplete implementations. But, it's a workable idea with, like most workable ideas in Java, writing a lot more code. I suspect that the Mewler tests will be seeing a completed version of this idea in order to write complete tests.
- 1 ↑ I, for one, am somewhat inconsistent about where I fail by returning null, or throwing an exception. Especially when dealing with input/output, what is exceptional?