Saturday, May 12, 2012

TDD - Testing AsyncTasks on Android

Testing functionalities of a software system is probably one of the most important things to do if you want to release a reliable application to your clients.

TDD (aka Test Driven Development) is a methodology where you create and develop unit tests before writing a single line of application logic. This strategy allows you to better understand the problem your app is solving, it gives you a more pragmatic and structured way to build a software component. It forces you to design and think to the solution in detail before coding it. And, most important, when you change something in your code, you can safely run your tests again and have an immediate result if your changes have affected the behavior of the component.

I usually do TDD to test model classes, database classes and networking. I'm not writing functional tests on the UI but Android offers you tools like monkey to test also user clicks on your UI.

If your code use AysncTasks to perform networking operations in a background thread, things become a little trickier.

Normally, in a AysncTask, you can specify a result of this task using onPostExecute.
This call is executed on the main thread so you can update your UI and notify the user easily.

AsyncTask runs your task in a background thread, but this opens some issues: our tests must be run on the main thread! We need to wait for the AsyncTask to finish before jumping to the next test or to quit the testing routine.
There is a method called runTestOnUiThread that perform a testcase on a main thread. To use such method, your class must extend InstrumentationTestCase.





In the previous code snippet, I'm creating a unit test called testGetMedia whose purpose is doing some networking in an asynchronous fashion, to fetch some data from a remote backend.
The tested class is called NetworkTasks and it contains a method called getMedia(Media) similar to this one:



GetAsyncMediaTask is a subclass of AsyncTask that implements the doInBackground() and the onPostExecute() methods. I'm not showing the code of GetAsyncMediaTask here because is not really interesting for our purpose. In the doInBackground() method an http call is placed and it blocks the calling thread until it gets an HttpResponse; inside the onPostExecute() method, I'm using the listener passed to the NetworkTasks object to notify the component that has launched it. The component is notified through onFail and onSuccess callbacks.
In the code snippet the listener is called GetMediaListener.
This covers almost everything except for few (important!) details: what happens in the GetMediaListener?




The GetMediaListener extends a generic CallbackListener which in turn implements a simple interface with two methods (onFail and onSuccess).
The callbacks are here used to assert that a condition holds or doesn't hold. The listener used in the real code will do some useful stuff on the UI, trigger some notifications and so on.
And here comes into play the CountDownLatch object. It is absolutely mandatory to use this object when you want to test an AsyncTask. Infact, even if we're running the task on the main thread, as soon as the doInBackground method ends, the testGetMedia() method ends too, and there is no way for the listener to be invoked by the system! By using a CountDownLatch, we impose the current thread to stop and wait until a certain condition is met: here we wait that the callback onPostExecute (which in turns call onFail or onSuccess) is executed: as soon as it is executed, the object is decremented with the countdown() method of the supreclass and the lock is removed. The lock is automatically removed after 30 seconds by the signal.await() call.

You can get all the code here: https://gist.github.com/2667108

Enjoy async TDD on Android! :)