Writing unit tests for single thread "logic" code is pretty easy. However, there are times when the code under test must execute on a background queue/ thread. This article shows a technique using "latches" to coordinate test execution between the test thread and a background queue (i.e. background thread).
What is a "Latch"?
In the context of this article, a "latch" is a simple primitive object that allows a thread to wait until another thread signals completion.
- Java "Latch" ->
java.util.concurrent.CountDownLatch
- ObjC "Latch" -> Grand Central Dispatch
dispatch_semaphore_t
Test Dance
The real power of using "latches" comes when setting up multiple latches to coordinate a deterministic dance between your test thread and background task.
Java and JUnit
I use java.util.concurrent.CountDownLatch
objects a lot when testing multi-threaded Java code. CountDownLatch
objects provide a very simple means of signaling between two threads.
Here is a short example demonstrating the technique in Java using JUnit.
@Test
public void latchTechnique() throws Exception {
// Create a latch with an initial value of 1. This simply means that
// that it will take one `countDown` method call to signal to an awaiting thread.
final CountDownLatch latch = new CountDownLatch(1);
// Create a serial queue to service a background task.
//
// NOTE: Yes, an ExecutorService actually gives us back a Future when submitting
// a task. However, to illustrate the "latch" technique, we will ignore that
// the Future API exists.
Executor service = Executors.newSingleThreadExecutor();
// Submit a task to the queue for execution on a background thread.
// In this example, the task simply sleeps for 1 second then
// counts down the latch to 0.
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("(B) Background task execution.");
// do something useful
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// handle interruption policy
}
System.out.println("(B) Background task will signal.");
// now signal that our background task completed
latch.countDown();
System.out.println("(B) Background task did signal.");
}
});
System.out.println("(T) Test thread will wait on latch...");
// Block the test thread for at most 5 seconds before giving up and failing the test.
boolean receivedSignal = latch.await(5, TimeUnit.SECONDS);
System.out.println(String.format("(T) Test thread did wait on latch (timeout=%b)", !receivedSignal));
assertEquals(true, receivedSignal);
}
Here is a snapshot of the output:
(T) Test thread will wait on latch...
(B) Background task execution.
(B) Background task will signal.
(B) Background task did signal.
(T) Test thread did wait on latch (timeout=false)
Here is a snapshot of the output if the background task does not complete within 5 seconds:
(T) Test thread will wait on latch...
(B) Background task execution.
(T) Test thread did wait on latch (timeout=true)
java.lang.AssertionError: expected:<true> but was:<false>
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.failNotEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:126)
at org.junit.Assert.assertEquals(Assert.java:145)
at com.briancoyner.FooTest.latchTechnique(FooTest.java:76)
Objective-C and OCUnit
The Grand Central Dispatch (GCD) framework provides a similar type of "latch" called dispatch_semaphore_t
. The example is nearly identical to the Java example above.
- (void)testLatchTechnique
{
dispatch_semaphore_t latch = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"(B) Background task execution.");
// do something useful
sleep(1);
NSLog(@"(B) Background task will signal");
dispatch_semaphore_signal(latch);
NSLog(@"(B) Background task did signal");
});
NSLog(@"(T) Test thread will wait on semaphore...");
long value = dispatch_semaphore_wait(latch, dispatch_walltime(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
NSLog(@"(T) Test thread did wait on latch (timeout=%@)", value == 0 ? @"NO" : @"YES");
dispatch_release(latch);
STAssertEquals(0L, success, @"Background task did not finish in time.");
}
Here is a snapshot of the output.
(T) Test thread will wait on semaphore...
(B) Background task execution.
(B) Background task will signal
(B) Background task did signal.
(T) Test thread did wait on latch (timeout=NO)
Here is a snapshot of the output if the background task does not complete within 5 seconds.
(T) Test thread will wait on semaphore...
(B) Background task execution.
(T) Test thread did wait on latch (timeout=YES)
error: -[HackTests testLatchTechnique] : '0' should be equal to '49': Background task did not finish in time.
Summary
The "latch" technique is actually quite useful when testing code that must execute on a background queue/ thread. You should consider how to utilize a "latch" the next time you put an arbitrary "sleep" in your test code. And, yes, I know you have added "sleeps" order "to give the background task enough time to execute".
Remember, the real power of using "latches" comes when setting up multiple latches to coordinate a deterministic dance between your test thread and background task.
Of course, you may have to re-design your classes to be "testable with latches". Often this means providing hooks for a mock subclass to intercept various calls in order to manipulate a "latch" object. Other times, you can simply subclass, override the main task method, call super, then manipulate the "latch" object. And finally, there are times when it is impossible to utilize this technique and you have to be creative in other ways.