Wednesday, November 13, 2013

Unit testing Android Handler using Robolectric

I was looking for ways to do unit testing my Android code. Since Android JUnit need to be executed in emulator or real device, I want another way to run my tests outside these environments.

Then I use actual Java JUnit and Robolectric to test my code, but the problem of this approach is some code that is need to be ran inside Android environment cannot actually running while testing.

In my case, I use Handler from background Thread to sends Message back to caller Thread. I want to test that the callback method is actually executed on caller Thread but the Message is never be sent in testing because of the test environment.

Here is snippet of my TargetClass code.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

public void process(TargetClassListener listener){

  //1 grab caller thread callback.
  mListener = listener;
  mCallbackHandler = new Handler(){
    @Override
    public void handleMessage(Message msg){
      super.handleMessage(msg);
      if(mListener != null){
        mListener.onSuccess();
      }
    }
  };

  //2 start processing in worker thread.
  HandlerThread ht = new HandlerThread("",android.os.Process.THREAD_PRIORITY_BACKGROUND){
    @Override
    public void run(){
      //1 do something.
      //2 notify onSuccess().
      Message msg = mCallbackHandler.obtainMessage();
      msg.what = TargetClassListener.CALLBACK_ON_SUCCESS;
      msg.sendToTarget();
    }
  };
  ht.start();

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

From the code above, the background Thread (HandlerThread ht) will send Message to mCallbackHandler which will force caller Thread to execute handleMessage() and execute mListener.onSuccess().

While test is running, the callback method onSuccess() won't be executed because the Message cannot be sent in testing environment.

So, instead of validate test result in onSuccess() (which cannot be reached here), I change to validate Handler object.

Here is snippet from my test code.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@Test
public void process_normalCase_willExecuteCallbackOnCallerThread(){

  //1 execute class under test.
  TargetClass tc = new TargetClass();
  tc.process(this);

  //2 wait for background thread to be done.
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    fail(e.toString());
  }

  //3 validate handler object.
  assertNotNull(tc.getCallbackHandler());
  ShadowHandler handler = (ShadowHandler) Robolectric.shadowOf(tc.getCallbackHandler());
  assertTrue(handler.hasMessages(CALLBACK_ON_SUCCESS));
  assertEquals(Thread.currentThread().getId(), handler.getLooper().getThread().getId());

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

At the third step, I get the Handler object from target class and wrap it inside Robolectric's ShadowHandler. Then validate the Message that the handler contains and the Thread id that associated with its Looper.

If these test cases are passed, then I can guarantee that the TargetClass will execute the right callback on the right Thread.

It's something, isn't it?  :-)

Sunday, June 9, 2013

Take a screenshot in Raspbian Wheezy

To take a screenshot in LXDE, you will need the application ImageMagick. You can download ImageMagick using apt tool.

sudo apt-get install imagemagick

Once ImageMagic is installed, you can take a screenshot using import command.

import -window root screen.png

You can also combine this command with the delay command to make a delay time before take a screenshot.

sleep 5; import -window root screen.png

This will be delayed for 5 seconds then take a screenshot. Combine sleep command is very handy since you might need to hide the terminal window before take a shot.

Reference: http://stackoverflow.com/questions/10581953/how-do-i-take-a-screenshot-on-a-raspberry-pi-running-debian-squeeze-and-lxde

Friday, June 7, 2013

Raspbian Wheezy keyboard mapping problem

I found my Raspbian Wheezy got a wrong keyboard mapping. When I type '@' and '#' symbol, it turns out to be other characters.

After searching in the internet (using DuckDuckGo in Raspbian :-D) I found this thread http://raspberrypi.stackexchange.com/questions/1042/why-is-my-symbol-not-working

So the problem is default keyboard layout is UK and I have to change it to US by change its configuration file using nano.

sudo nano /etc/default/keyboard

change XKBLAYOUT value from "gb" to "us"

then restart the PI

sudo /sbin/reboot

after my PI is wake up again, keyboard layout is changed to US and now problem solved.