This post originated from an RSS feed registered with Agile Buzz
by Joe Walnes.
Original Post: Unit Testing Asynchronous Code
Feed Title: Joe's New Jelly
Feed URL: http://joe.truemesh.com/blog/index.rdf
Feed Description: The musings of a ThoughtWorker obsessed with Agile, XP, maintainability, Java, .NET, Ruby and OpenSource. Mmm'kay?
I try to avoid using code that instantiates threads from unit-tests. They're awkward to write, brittle and I would rather extract the controlling thread, using the test as the controller. However there are times when it's unavoidable.
Here's an example. A PriceFinder is a class that goes and retrieves a price for a symbol asyncronously, returning immediately. Sometime later it receives a response and performs a callback on a handler. I've left out the implementation of how it actually does this (maybe a web-service, database call or JMS message).
public class PriceFinder {
public void findPrice(String symbol, PriceHandler handler) { ... }
}
public interface PriceHandler {
void foundPrice(Price price);
}
To test this, a handler can be used from the test case that records what is passed in. The test can then wait for a specified time and assert that the correct result is received. If the asyncronous code does not complete within the specified time, the test will fail (suggesting the code is either running very slowly or is never going to complete).
public class PriceFinderTest extends TestCase {
private PriceFinder finder = new PriceFinder();
private Price receivedPrice;
public void testRetrievesAValidPrice() throws Exception {
finder.findPrice("MSFT", new PriceHandler() {
public void foundPrice(Price price) {
receivedPrice = price;
}
});
// Smelly!
Thread.sleep(2000); // Wait two seconds.
assertNotNull("Expected a price", receivedPrice);
}
}
However, this sucks as it will slow your test suite right down if you have loads of tests using Thread.sleep().
A less time consuming way to do it is by using wait() and notify() on a lock. The handler notifies the lock and the test waits for this notification. In case this notification never happens, a timeout is used when calling wait().
public class PriceFinderTest extends TestCase {
private PriceFinder finder = new PriceFinder();
private Price receivedPrice;
private Object lock = new Object();
public void testRetrievesAValidPrice() throws Exception {
finder.findPrice("MSFT", new PriceHandler() {
public void foundPrice(Price price) {
receivedPrice = price;
synchronized(lock) {
lock.notify();
}
}
});
synchronized(lock) {
lock.wait(2000); // Wait two seconds or until the
// monitor has been notified.
// But there's still a problem...
}
assertNotNull("Expected a price", receivedPrice);
}
}
This optimistic approach results in fast running tests while all is good. If the PriceFinder is behaving well, the test will not wait any longer than the PriceFinder takes to complete its work. If PriceFinder has a bug in it and never calls the handler, the test will fail in at most two seconds.
However, there's still a subtle issue. In the case that the PriceFinder is really fast, it may call notify() before the test starts wait()ing. The test will still pass, but it will wait until the timeout occurs.
This is where threading and synchronization get messy and beyond me. Doug Lea has a nice little class called Latch in his concurrency library (available in JDK 1.5 as CountDownLatch). A latch can only be locked once, once released it will never lock again.
public class PriceFinderTest extends TestCase {
private PriceFinder finder = new PriceFinder();
private Price receivedPrice;
private Latch latch = new Latch();
public void testRetrievesAValidPrice() throws Exception {
finder.findPrice("MSFT", new PriceHandler() {
public void foundPrice(Price price) {
receivedPrice = price;
latch.release();
}
});
latch.attempt(2000); // Wait until the latch is released
// or a timeout occurs.
assertNotNull("Expected a price", receivedPrice);
}
}