Many GUI environments have some means or another of “fuzz” or “bash” testing, where some test driver executes a bunch of random input, in hopes of catching errors (e.g., missing validation logic). Android offers the Test Monkey for this.
Many GUI environments have some means or another of scripting GUI events from outside the application itself, to simulate button clicks or touch events. Android offers MonkeyRunner for this.
As the names suggest, there is a bit of commonality in their implementation. And, as you might expect, there is a bit of commonality in their coverage in this book — we will examine both MonkeyRunner and the Test Monkey in this chapter.
Understanding this chapter requires that you have read the core chapters of this book.
MonkeyRunner is a means of creating test suites for Android applications based on scripted UI input. Rather than write a series of JUnit test cases or the like, you create Jython (JVM implementation of Python) scripts that run commands to install apps, execute GUI events, and take screenshots of results.
The primary object you will work with in a MonkeyRunner script is a MonkeyDevice
,
which represents your connection to the device or emulator that you are testing.
You obtain a MonkeyDevice
by calling waitForConnection()
on MonkeyRunner
; this will
return once it has established a connection.
From there, MonkeyDevice
lets you send events to the device or emulator:
installPackage()
allows you to install an APK from your development machine, and
removePackage()
allows you to get rid of itstartActivity()
and broadcastIntent()
allow you to start up components of your
apppress()
to simulate key events, including QWERTY keys, standard device keys
like BACK, D-pad/trackball events, and anything else represented by a standard
Android KeyEvent
type()
to simulate entering a whole string, as a simplification over calling
press()
once per lettertouch()
and drag()
let you simulate touch eventsThe biggest limitation is in getting data out of the device, to determine if your test worked successfully. Your options are:
takeSnapshot()
, which will capture a screenshot that you can save to disk, compare
with other screenshots, etc.shell()
executes adb shell
commands, returning any resultsUnlike JUnit-based testing, you have no visibility into the activity beyond what appears on the screen — you cannot inspect widgets, call methods, or the like.
For example, here is a script that installs an app, runs an activity from it, and presses the down button on the D-pad three times:
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
device = MonkeyRunner.waitForConnection()
device.installPackage('bin/JUnitDemo.apk')
device.startActivity(component='com.commonsware.android.abf/com.commonsware.android.abf.ActionBarFragmentActivity')
device.press('KEYCODE_DPAD_DOWN', MonkeyDevice.DOWN_AND_UP)
device.press('KEYCODE_DPAD_DOWN', MonkeyDevice.DOWN_AND_UP)
device.press('KEYCODE_DPAD_DOWN', MonkeyDevice.DOWN_AND_UP)
# result = device.takeSnapshot()
# result.writeToFile('tests/monkey_sample_shots/test1.png', 'png')
To execute your MonkeyRunner script, have your device or emulator set up at a likely
starting point (e.g., home screen), then execute the monkeyrunner
command,
passing it the path to your script (e.g., monkeyrunner monkey_sample.py
). You will
see the script executing on the screen of your device or emulator, and your console
will contain whatever output you might emit from your test script itself. For example,
you might take screenshots, compare them against a master copy (using methods
on MonkeyImage
to help with this), and emit warnings if they differ unexpectedly.
Independent from the JUnit system and MonkeyRunner is the Test Monkey (referred to here as “the Monkey” for short).
The Monkey is a test program that simulates random user input. It is designed for “bash testing”, confirming that no matter what the user does, the application will not crash. The application may have odd results — random input entered into a Twitter client may, indeed, post that random input to Twitter. The Monkey does not test to make sure that results of random input make sense; it only tests to make sure random input does not blow up the program.
You can run the Monkey by setting up your initial starting point (e.g., the main activity in your application) on your device or emulator, then running a command like this:
adb shell monkey -p your.package.here -v --throttle 100 600
(substituting the application ID of a project on your device or
emulator for your.package.here
)
Working from right to left, we are asking for 600 simulated events,
throttled to add 100 millisecond delays. We want to see a list of the
invoked events (-v
) and we want to throw out any event that might
cause the Monkey to leave our application, as determined by the
application ID (-p your.package.here
).
Note that this truly is the application ID, not the package name.
In simple apps — such as most of this book’s samples — the package
name is the application ID. But if you have modified the application
ID in your build.gradle
file (e.g., replaced the application ID
for a product flavor), you will need to use the actual application ID
here, not the package name. In general, despite any legacy documentation
to the contrary, “package name” only affects code-generated classes
like R
and BuildConfig
. Any other use of the term “package name”
really is referring to the application ID.
The Monkey will simulate keypresses (both QWERTY and specialized
hardware keys, like the volume controls), D-pad/trackball moves, and
sliding the keyboard open or closed. Note that the latter may cause
your emulator some confusion, as the emulator itself does not itself
actually rotate, so you may end up with your screen appearing in
landscape while the emulator is still, itself, portrait. Just rotate
the emulator a couple of times (e.g., [Ctrl>-<F12>
) to clear up the
problem.
Also note that the throttle time is only used in between batches of
related events. So, a batch of several touch events, or a pair
of up/down events for a hardware key, will not be throttled. You can
see this if you pass -v -v -v
for “ultimate verbose mode” and look
at the output, such as this snippet:
:Sending Key (ACTION_DOWN): 134 // KEYCODE_F4
:Sending Key (ACTION_UP): 134 // KEYCODE_F4
Sleeping for 100 milliseconds
:Sending Touch (ACTION_DOWN): 0:(1302.0,842.0)
:Sending Touch (ACTION_MOVE): 0:(1295.3395,838.0863)
:Sending Touch (ACTION_MOVE): 0:(1290.1539,827.0493)
:Sending Touch (ACTION_MOVE): 0:(1280.0454,826.9068)
:Sending Touch (ACTION_MOVE): 0:(1272.0161,816.8062)
:Sending Touch (ACTION_MOVE): 0:(1260.7244,810.8302)
:Sending Touch (ACTION_UP): 0:(1250.5455,801.67444)
Sleeping for 100 milliseconds
:Sending Key (ACTION_DOWN): 19 // KEYCODE_DPAD_UP
:Sending Key (ACTION_UP): 19 // KEYCODE_DPAD_UP
Sleeping for 100 milliseconds
:Sending Key (ACTION_DOWN): 68 // KEYCODE_GRAVE
:Sending Key (ACTION_UP): 68 // KEYCODE_GRAVE
Sleeping for 100 milliseconds
Here, we see actual messages where the throttling is applied (“Sleeping for 100 milliseconds”). Hence, the time it takes the Test Monkey to run a test will be at most the number of events times the throttle time, but due to event batching, it is usually substantially less than that. 25-30% of the maximum time seems typical.
For playing with a Monkey, the above command works fine. However, if you want to regularly test your application this way, you may need some measure of repeatability. After all, the particular set of input events that trigger your crash may not come up all that often, and without that repeatable scenario, it will be difficult to repair the bug, let alone test that the repair worked.
To deal with this, the Monkey offers the -s
switch, where you
provide a seed for the random number generator. By default, the
Monkey creates its own seed, giving totally random results. If you
supply the seed, while the sequence of events is random, it is random
for that seed — repeatedly using the same seed will give you
the same events. If you can arrange to detect a crash and know what
seed was used to create that crash, you may well be able to reproduce
the crash.