Monday, March 12, 2012

monkeyrunner: running unit tests

First of all, I hope you like the new look of the blog. Personally I think the darker background turns the posts more relevant and less distracting.


Let me know what you think.


Now, to the point. We have presented an analyzed here many monkeyrunner scripts and techniques but we haven't explicitly created some unit tests, and this is precisely what we are going to do.


As our Application Under Test (AUT) we are using the well-known Temperature Converter, which was used many times to demonstrate other concepts as well.
The source code is as usual available through github.


Additionally, we are also using the help of AndroidViewClient which is available at github too and was introduced in monkeyrunner: interacting with the Views. The central idea is to use AndroidViewClient facilities to create a unit test. Let's name this script temperature-converter-view-client.mr.

#! /usr/bin/env monkeyrunner

'''
Created on 2012-03-08

@author: diego
'''

import sys
import unittest
import subprocess
import socket
import re
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
import viewclient

class TemperatureConverterActivityTests(unittest.TestCase):

   def setUp(self):
      # connect to the device
      self.device = MonkeyRunner.waitForConnection(60)
      self.assertNotEqual(None, self.device)
      self.device.wake()

      # start TemperatureConverter
      self.device.startActivity(component="com.example.i2at.tc/.TemperatureConverterActivity")

      # clear the field
      for n in range(10):
         self.device.press('DEL', MonkeyDevice.DOWN_AND_UP)

      # create the client
      self.viewclient = viewclient.ViewClient(self.device)


   def tearDown(self):
      pass

   def testConversion(self):
      C = '123'
      F = '253.4'

      MonkeyRunner.sleep(1)
      self.device.type(C)
      MonkeyRunner.sleep(1)

      self.viewclient.dump()

      celsius = self.viewclient.findViewById('id/celsius')['mText']
      fahrenheit = self.viewclient.findViewById('id/fahrenheit')['mText']

      self.assertEqual(celsius, C)
      self.assertEqual(fahrenheit, F)


if __name__ == '__main__':
   unittest.main()

It is noticeable how the use of AndroidViewClient simplifies this test.


So if everything goes well, providing that your emulator or device is running and reachable, you will receive this output for the test run:

$ ./temperature-converter-view-client.mr 
.
----------------------------------------------------------------------
Ran 1 test in 8.801s

OK

Hope this helps.

16 comments:

jaylen watkins said...

Thanks for this unique testing.

Sample Assessments

Unknown said...

Hi Diego,
i was wondering how can i automate certain tests like, playing audio/video multiple times(locally or streaming). when we use adb shell, we can use "am start" commands(eg: am start -d -n package name).
how can i do the same in monkeyrunner? i know there is command called "shell" that can help me do this, is there any other way?
Thanks in advance,
Avinash

Diego Torres Milano said...

@Avinash Rao,
Yes, there's another way when you use monkeyrunner, despite the fact you mentioned of MonkeyDevice.shell() still being a valid option.
This alternative is MonkeyDevice.startActivity().

Unknown said...

Hi Diego,

so if i want to convert the command:
am start -d /mnt/sdcard/lighters.mp3 -n com.android.gallery3d/com.android.gallery3d.app.MovieActivity
to monkeyrunner format, how will it look?
I tried this,
device.startActivity('ACTION_RUN','/mnt/sdcard/lighters.mp3','com.android.gallery3d/com.android.gallery3d.app.MovieActivity')
but its not working.
-Avinash Rao

Unknown said...

Hey Diego,

I found out the correct command :)

device.startActivity(action='android.intent.action.VIEW',data='/mnt/sdcard/397b_av_h264_bl_640x480_15fps_1Mbps_aac_128kbps.3gp',component='com.google.android.gallery3d/com.android.gallery3d.app.MovieActivity')

was doing it wrong previously.
NOTE: i am using a different package and intent also a different file, from my previous comment.

Thanks a lot for helping Diego, will contact you if i need more info
-Avinash Rao

Unknown said...

Diego,

Is it possible to run multiple monkey runner scripts that have different unit test cases inside them. I want to be able to run 2 sets of scripts of 2 different devices. I need to start script1 on device1 and later if needed i need to start script2 on deivce2,is there a way to acheive this currently as i understand monkey runner can only handle multiple devices if they are in same script. Not 2 separate instances carrying 2 separate scripts.Please help

Diego Torres Milano said...

@Mallik Mahankali,
You are right, there's no problem creating connections for several devices in a monkeyrunner script but running several scripts concurrently usually lead to exceptions and broken connections. I think the best approach to cope with this limitation is to handle the logic inside a single monkeyrunner script (you can also import other tests) which hold the connections to the devices.

Vincent said...

Hi Diego,
Imagine you have 2 tests on this TemperatureConverterActivityTests class.
I think it will get stuck during the 2nd tests at setUp method. It will hang in self.device = MonkeyRunner.waitForConnection(60)
I guess the reason is that it tries to connect to a device that is already connected (i am not sure though)
Do you have ideas on how to handle that?

Thanks

Unknown said...

@Diego,
Issue seems to happen due to same view server port being assigned by monkeyrunner to multiple devices only when running 2 scripts.Can you help me understand how to control logic to connect all devices in 1 script. do u mean some ting like this

test1:
check a config file to see if phone and test cases are needed and if yes run them

repeat same for test2, test3...

Unknown said...

Diego,
Is there a way to use AndroidViewClient on a real device?

Diego Torres Milano said...

@Unknown,
Yes, it can be run on real rooted devices as long as the variables ro.secure and ro.debuggable have the right values.
This one-liner can be used to test the conditions:

$ (eval $(adb -s shell getprop | egrep '\[ro\.secure]|\[ro\.debuggable]' | sed 's/\./_/g; s/]: /=/g; s/[][\r]//g'); [[ 1 == $ro_secure && 0 == $ro_debuggable ]] && echo "System is secure: AVC won't work" || echo "System is not secure: AVC will work")

I think I will add this test to AVC.

Diego Torres Milano said...

In the previous comment it's: -s <serialno> but blogspot removed it

Zellman said...

Hi Diego,

In your example, there is only one test in the class. If you had had multiple tests in your class, would you still have waitForConnection or connectToDeviceOrExit in the setUp? I would think that only doing this once would be ideal instead of before every test.

Just curious of your opinion.

Thanks,
Andrew

Zellman said...

Also, I see that you use findViewById on your viewclient object. I have yet to have success with this method. The only IDs that show up for me are the unique IDs, which I will have no knowledge of at the time I write the test script. Is this a known effect of AndroidViewClient? In comparison, I can getViewById using MonkeyDevice, but getLocation doesn't work on the MonkeyView class. The most useful workflow I've found is to leverage the text field and use AndroidViewClient with findViewWithTextOrRaise. Still, I'm unable to use it successfully with RegEx, so far, but that may be user error.

Thanks again for all the posts, it has been very helpful!
-Andrew

Rafael Lima said...

Hi Diego,

Your AndroidViewClient is simply awesome. I do have a little problem with the MonkeyRunner.waitForConnection. If I have two tests cases, the code gets stuck in the second time it is running the waitForConnection. I tried this on your coe and I managed to reproduce. Do you have any thoughts?

appreciate it

Diego Torres Milano said...

@Rafael Lima,
There should be a single ViewClient.connectToDeviceOrExit() or MonkeyRunner.waitForConnection() per script.