Sunday, May 13, 2007

Unit Testing Multi-Threaded Code with SerialExecutorService

Writing unit tests for multi-threaded code is typically considered a difficult, sometimes impossible task. Recently I created an executor service that executes objects serially, that is, on the calling thread. This has been a huge help to testability. If you need to unit test code that spawns threads then I highly consider using an an implementor of java.util.concurrent.ExecutorService like this one.

Typical code that spawns threads looks something like this:

final class WorkerObject {
public void doWork() {
final Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("doing some work...");
}
});
thread.start();
}
...
}

And unit tests for code like this often looks like this:


WorkerObject obj = new WorkerObject();
obj.doWork();
Thread.sleep(1000); //sleep 1 second
Assert.assertTrue(obj.success());

This unit test may or may not pass based on the speed of the build server. Also, as unit test suites get longer and longer, and adding time like this is not the best thing to do. But beyond all that, the Java 5 ExecutorService framework is the replacement to spawning threads manually and provides many more features than manipulating threads directly.

The original threading code is better written as:

final class WorkerObject {
private final ExecutorService exeService;

public WorkerObject(ExecutorService exeService) {
this.exeService = exeService;
}

public void doWork() {
exeService.submit(new Runnable() {
public void run() {
System.out.println("doing some work...");
}
});
exeService.shutdown();
}
...
}

And the associated unit test:

WorkerObject obj = new WorkerObject(new SerialExecutorService());
obj.doWork();
Assert.assertTrue(obj.success());

Brian Goetz, author of Java Concurrency in Practice recommends thinking of the executor framework as the replacement for Thread.start( ).

For Unit Testing, using an ExecutorService instead of a Thread allows you to change how jobs are executed during unit tests. The SerialExecutorService is an ExecutorService that does not spawn new threads and instead executes all tasks serially on the calling thread. This means you can write your unit test without the Thread.sleep( ) call.

The downside to adding the ExecutorService to the object constructor is that the object is harder to construct. But if Spring is used to inject the ExecutorService then you get the added benefit of exposing the thread pool configuration to the XML configuration file... which can be very useful to performance tune the thread pool without recompiling and deploying. The Spring file can use the static convenience methods on the Executors object to create the executor service. This example shows how to use Executors.newFixedThreadPool with a pool of 10 threads with Spring 1.2:

<bean id="MyDefaultExecutor" singleton="false" autowire="no" class="java.util.concurrent.Executors" method="newFixedThreadPool">
<constructor-arg index="0" value="10">
</constructor-arg>
</bean>

Using the ExecutorServices will make your code much easier to maintain in the long run and much easier to test in the short run.

4 comments:

Mathew said...
This comment has been removed by the author.
Hamlet D'Arcy said...

Sorry... this post was originally published to people with access to my code base. SerialExecutorService just implements ExecutorService. Every time a method passed a runnable, just call run() on it w/out spawning a new thread. Every time you receive a Callable, just call call() on it, etc. etc.

Mathew said...

Thanks a lot. It was blatantly obvious after i though about it for a second ;) Great article by the way, so simple yet so effective...

Hamlet D'Arcy said...

The SerialExecutorService is now part of JConch 1.1.

http://is.gd/hx0A