Unit testing asynchronous code

Today I wrote on a first prototype for my current thesis research. I started coding just ahead however - at the same time - trying to find a good design which has a high potential to be reused for the final system. Modularity, decoupling etc. are main goals of course and since the prototype interacts with the Bluetooth API one of my main aims was to try to separate the logic dealing with it as much as possible from the rest of the application. This separation gives several benefits: for accessing the device's bluetooth stack, access to native libraries is necessary. So depending on whether your app runs on Linux, Windows or OSX you may have to rely on different libraries.

Again, going with TDD you're best served. And actually this is where the Test-Driven-Design (instead of the commonly used "development") comes to play. By starting with your test and by thinking on how to test your logic in the simplest way, you're most likely to come out with a testable and consequently nicely decoupled system. But when you have asynchronous code, things get messy again. How to you test asynchronous code?? Assume the following (I'm using Java here, but could refer to it with C# in the same way)
@Test
public void testSomeLogic(){
assert(...); //assert some state

myObj.executeSomething(); //assume this is executed asynchronously

assert(...); //assert the result of the execution
}
Now for sure you agree that the 2nd assert which should check the result of the execution of "executeSomething()" will probably be called before the asynchronous code executed inside "executeSomething()" finishes processing. Consequently the test will always fail.

Browsing the web brought me through some interesting approaches to handle this problem.  The best way of course is to try to mock out this asynchronous stuff as much as possible s.t. you can avoid having it in your test cases. But often (as also in my situation) this wasn't just possible. So a solution is basically to wait for the async call to finish. How is this done?
Let's outline my situation more clearly. I have a controller class which has the following
public class BluetoothDevicesController{
private IBluetoothDeviceDiscoveryAgent discoveryAgent;
...

public BluetoothDevicesController(IBluetoothDeviceDiscoveryAgent discoveryAgent){
this.discoveryAgent = discoveryAgent;
}

public void startDiscovery(){
Timer timer = new Timer();
timer.schedule((TimerTask)discoveryAgent, 0, 5000); //reruns the thread every 5 secs
}

...
}
This is more or less the outline of the controller which is subject to my test. The startDiscovery() method starts the asynchronous execution by rescheduling the device discovery every 5 seconds.
Note: the class doing the "low-level" Bluetooth interactions is injected in the constructor and separated through an appropriate interface. The controller is not aware of the Bluetooth interactions.
The separation with an interface allows the creation of a mock which can be used in the unit test to avoid the Bluetooth logic. So let's come to the implementation of the Mock + test.
public class MockBluetoothDeviceDiscoveryAgent extends TimerTask implements IBluetoothdeviceDiscoveryAgent{
...

public void run(){
eventListener.onDeviceDiscoveryFinished(devices);
synchronized(this){
notifyAll();
}
}
}
The test case has the following structure
public class BluetoothDevicesControllerTest{
...
@Test
public void testDevicesDiscovery(){
assert(...) //initial check

deviceDiscoveryController.startDiscovery(); //the async method

synchronized(mockDiscoveryAgent){
mockDiscoveryAgent.wait(2000); //set a timeout of 2 seconds
}

assert(...) //final check
}
...
}
Some comments: The notifyAll() in the mock object notifies all threads which went into sleep previously to wake up. The one with highest priority will continue processing. The synchronized block is needed since notifyAll can just be called within it.
The according test case does the opposite. It uses the synchronized to go into the wait state and sleeps for a timeout of 2 seconds. In the mean time, if the notifyAll gets called, the test case continues its work.
Kindle

Comments

0

Your ad here?