Carlos Sanchez
Carlos Sanchez

Reputation: 61

How to Unit test the following method

I am taking a Software Testing course as part of my elective courses for Computer Science.

Right now we are testing software made by graduate students, and its not really pretty, there is this class I am currently testing that has this the following method, at first it seems apparent that it uses threads and it seems that is creating a listener to listen over a port, can someone explain me what is this piece of code doing? How would I go about testing this function?

public void startServer() throws IOException  {
  ServerSocket ss = new ServerSocket(portNum);
  while(true) {
    Socket s = ss.accept();
    Thread t = new Thread(new ConnectionHandler(s));
    t.start();
  }
}

Upvotes: 0

Views: 277

Answers (2)

GhostCat
GhostCat

Reputation: 140613

In short: the code creates a new ServerSocket to listen on a specific port; and when a "request" comes in on that port, it starts a thread to handle that client.

The problem with that code:

  • It runs a while(true) loop; so that method is not supposed to ever come back
  • And beyond that, it is written in a hard to test way; basically because you have those two calls to new in that method.

I will explain how you could overcome the second part; and then lets talk about the first point. Regarding "testing" itself, you have two options:

  1. Lock into PowerMock (ugly); or maybe Mockito spies could help; to mock those calls to new. (Mockito is OK, but PowerMock is not so much in my eyes)
  2. Preferred: change your code to be easy to test; and then use dependency injection.

Like:

public class Server {
  private final SocketFactory socketFactory;
  private final ThreadFactory threadFactory;

  public Server() {
    this(new SocketFactory(), new ThreadFactory());
  }

  Server(SocketFactory socketFactory, ...
    this.socketFactory = socketFactory...

public void startServer() throws IOException
{
    ServerSocket ss = socketFactory.createSocketFor(portNum);
    while(true)
    {
        Socket s = ss.accept();
        Thread t = threadFactory.newThreadFor(new ConnectionHandler(s));
        t.start();
    }
}

And now ... everything is super simple: you can use that second package-protected ctor to insert mocked factories; and then you can configure/verify that those factories see the calls you expect.

Of course, this might look like "more" work; as now you have to create those two other classes (and actually you might use interfaces plus impl classes). But thing is: you end up with a better design, that is not only easier to test, but also easier to maintain and enhance.

And then: creating "bare metal" threads is not really a good practice any more. ( especially not in a while-true loop; in case you are still searching your bug ) You should rather look into some ThreadPool class; to make sure that you are not constantly creating new threads. Those are "expensive"; you should very much prefer to "re-use" threads. And there are libraries that help with that!

OK, coming back to the other problem: as of now, you simply can't reasonable unit test this method because of the while(true). You see, when you mock that ServerSocket, then a call to accept() will not block; and you ran into some infinite loop creating mocked threads.

So: you either have to rework this code (to allow to stop it externally) ... or you could setup the ThreadFactory mock to return a mocked thread; that throws some specific exception. And then your unit test simply expects that exception to be thrown - as indirect "proof" that the things you expected really took place.

Upvotes: 4

Nathan Hughes
Nathan Hughes

Reputation: 96444

What this code does is listen at a port. Each time a client connects, it delegates the work to a new thread. That way the server socket stays available to serve more requests.

Testing the connection handler may be do-able, it seems likely the connection handler contains the code that most needs testing. For the rest of it, you will have to create client threads that connect to this socket, and at that point what you're writing isn't a unit test. Code like this that directly instantiates classes (rather than injecting or passing in objects that implement an interface) makes it hard to substitute mocks.

I would plan to write unit tests to thoroughly cover the connection handler, then create a test harness (a separate program you can run in another jvm) to create threads to connect to this.

Upvotes: 1

Related Questions