Thursday, July 25, 2013

Automating Android Testing

So, I am dealing now with improving the performance of an Android app I am working on.
The app is an education app where users can view their books, collaborate and do many more things. There are many things happening in this app, and some of them negatively impact the user experience in terms of performance and responsiveness.
As I am approaching the task, I decided that I must have some ways to measure the current performance of the app, and its various parts.
I basically want to perform a user operation, and measure the time it takes the app to respond.
Assuming the way this works is:
User Input -> Various lengthy operations -> Display updated
If I can improve the lengthy operations performance, I will get a better user experience.
To establish a method for measuring the performance and any progress I make, and not just basing this on subjective feeling, I wanted to make some automated framework, whereby a script can activate user actions, and report back the time it took the app operations until the desired feedback is presented to the user.
There are various methods described elsewhere, some rely on accessibility and automating various widget actions based on that. In my case, I do not have widgets, and I wanted a simpler way.
So my simple solution consists of a small footprint web server (or http daemon) embedded in my app. I based this on Nano HTTPD: https://github.com/NanoHttpd/nanohttpd which is a very simple http daemon you use by subclassing the NanoHTTPD class.
When you subclass NanoHTTPD, you are expected to override the serve method to respond to http requests.

I wrote mine to look for parameters in the query string this way:



public Response serve(String uri, Method method, Map headers,
   Map parms, Map files) {
 for (Map.Entry kv : parms.entrySet()) {
  String key = kv.getKey();
  String strValue = kv.getValue();
  if (key.compareTo("cmd") == 0) {
   _callbackHandler.TA_PerformCommand(strValue);
   break;
  }
  else {
   Log.d("httpd", "Invalid param: " + key);
  }
  final String html = "Command Received";
  return new NanoHTTPD.Response(Response.Status.OK, MIME_HTML, html);
 }

The callback handler is an object passed to the constructor of my NanoHTTPD sub-class.

Since some of the operations I try to measure take time, and I want to measure that time, I added logic to my serve method to wait and sleep (since it is anyway being run on a separate thread) until the operation is done, and to report back the timing of this operation. This is the basic code:



public Response serve(String uri, Method method, Map headers,
   Map parms, Map files) {
 for (Map.Entry kv : parms.entrySet()) {
  String key = kv.getKey();
  String strValue = kv.getValue();
  if (key.compareTo("cmd") == 0) {
   startMeasurements();
   _callbackHandler.TA_PerformCommand(strValue);
   while (_inMeasurement) {
    try {
     Thread.sleep(1L, 0);
     long delta = System.currentTimeMillis() -
        _startTime;
     if (delta > 6000L) { // timeout after 6 sec
      addMeasurement("timeout");
      endMeasurements();
     }
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   String htmlResult = measurementsToHTML();
   return new NanoHTTPD.Response(Response.Status.OK,
       MIME_HTML, htmlResult);
  }
  else {
   Log.d("httpd", "Invalid param: " + key);
  }
  final String html = "Command Received";
  return new NanoHTTPD.Response(Response.Status.OK, MIME_HTML, html);
 }


The callback handler's TA_PerformCommand calls my server class endMeasurements once the operation is done setting the _inMeasurement flag to false.
If the action takes more than 6 seconds (a safe upper limit in my case), the loop times out and we end the measurement.

Once I have this little server set in my app, it is quite easy to write a testing script using JMeter, or a nice little app I found for my Mac: http://fakeapp.com/

It is now quite easy for me to repeat a sequence of tests, capture the times, tweak some of the code and test again.