|
I have been working on a new test library for Google Android
lately, a project which I’m very excited about, and which I feel has loads of potential:
Calculon, an Android
testing library which lets you write functional tests for your Activities using
a very concise and natural syntax.

I am a big fan of
test-driven
development, and I think there’s no arguing about the massive benefits you get
from backing your software up with an automated test suite. The support for writing
tests in Android, however, is lacking at best. Even though there has been a test
runner since the 1.0 days, writing unit or functional tests in Android is far from
being a pleasant experience. For me, the two most important things about tests are
that they must be easy to write, and more importantly, easy to read.
That being said, here is a classic example of why Android testing sucks. Our
test story is:
Assert that a click on ‘button’ launches a new BarActivity.
Without Calculon, what you do is this:
01 |
public
void
whatInHeavensNameDoesThisEvenTest()
{ |
02 |
final
Button b = (Button) getActivity().findViewById(R.id.button); |
03 |
ActivityMonitor monitor = getInstrumentation().addMonitor( |
04 |
BarActivity.class.getCanonicalName(),
null,
false); |
05 |
getInstrumentation().runOnMainSync(new
Runnable() { |
10 |
getInstrumentation().waitForIdleSync(); |
11 |
assertTrue(getInstrumentation().checkMonitorHit(monitor,
1)); |
So, I first have to retrieve a reference to the button that’s supposed to launch
a new BarActivity. Then I have to create what Android calls an
ActivityMonitor, that’s simply an object which just sits there and waits until
an Activity is being launched that matches the description it was configured with.
Then I have to perform the actual button click, but sorry, not allowed on the instrumentation
thread, so I have to runOnMainSync(). We’re not yet there though: we have to sit
and wait, because launching a new activity could take a while, so we also have to
waitForIdleSync(). The assertion that follows also couldn’t be less expressive.
When I first wrote this test, my single thought was: Where the heck is
my story? It’s buried under a heap of ugly boilerplate code.
I don’t care that the test runner cannot perform a view event on the
instrumentation thread. I don’t care that it needs to sit and wait for
the Activity to launch. I don’t care about ActivityMonitor and its crummy
API. I just want to look at the test, and understand what it’s doing.
Let’s reiterate our test story:
Assert that a click on ‘button’ launches a new BarActivity.
With Calculon, this test is written as:
assertThat(R.id.button).click().starts(BarActivity.class);
Oh the beauty. Our test story was a single sentence, so is the test. That’s how
it should be, really. There’s nothing worse than seeing someone else’s tests failing
on the build server and having to dissect them line by line to figure out what it’s
actually testing.
So let’s move on and look at some nifty Calculon tests. And remember: “Calculon
never does two takes!”
Testing with Calculon
Calculon is in a very early stage of development, and currently it only supports
writing functional Activity tests, using Android’s
ActivityInstrumentationTestCase2. Here is what a Calculon test looks like:
1 |
public
class
FooTest
extends
FunctionalTest<FooActivity> { |
3 |
super("com.example",
FooActivity.class); |
6 |
public
void
testStuff() { |
Nothing special here, really. You just inherit from a Calculon FunctionalTest.
Assertions
At the heart of a Calculon test are assertions. To create a new assertion, you
use the assertThat() method:
01 |
public
void
testStuff() { |
04 |
assertThat(getActivity())... |
05 |
assertThat(someOtherActivity)... |
08 |
assertThat(R.id.some_button)... |
09 |
assertThat(someButton)... |
For now, there are two basic kinds of assertions: ActivityAssertion and ViewAssertion.
There is also a third kind, ActionAssertion, but it takes a somewhat special role
and is discussed at the end.
Activity Assertions
An ActivityAssertion is an assertion on the state of an Activity and is used
like this:
01 |
public
void
testStuff() { |
03 |
assertThat().inPortraitMode(); |
06 |
assertThat().viewExists(R.id.some_button); |
09 |
assertThat().keyDown(KeyEvent.KEYCODE_BACK)... |
12 |
assertThat().satisfies(new
Predicate<Activity>() { |
13 |
public
boolean
check(Activity target) { |
14 |
return
target.isTaskRoot(); |
View Assertions
A ViewAssertion is basically the same, just that it tests the state and behavior
of a view:
01 |
public
void
testStuff() { |
03 |
assertThat(R.id.button).isVisible(); |
04 |
assertThat(R.id.button).isGone(); |
07 |
assertThat(R.id.button).keyDown(KeyEvent.KEYCODE_BACK)... |
08 |
assertThat(R.id.button).click()... |
09 |
assertThat(R.id.button).longClick()... |
12 |
assertThat(R.id.button).satisfies(new
Predicate<View>() { |
13 |
public
boolean
check(View target) { |
14 |
return
target.getVisibility() == View.VISIBLE; |
Action Assertions
This is a special kind of assertion. You always use it when you want to assert
that some action (e.g. a click or a key press) does something. You can use it on
both activity and view assertions, and it also allows you to delegate an assertion
to another object:
01 |
public
void
testStuff() { |
03 |
assertThat(R.id.b1).click().starts(BarActivity.class); |
06 |
assertThat(R.id.b2).keyDown(KeyEvent.KEYCODE_Q).finishesActivity(); |
09 |
assertThat(R.id.b3).click().implies(R.id.b2).isGone(); |
10 |
assertThat(R.id.b4).click().implies(getActivity()).inLandscapeMode(); |
11 |
assertThat(R.id.b5).click().implies(new
Predicate<Model>() { |
12 |
public
boolean
check(Model target) { |
13 |
return
target.someAttribute() ==
5; |
There’s more to come!
This is only a very early version. I intend to expand this library significantly
over time. Of course it’s open source, so contributions are always welcome. You
can fork the project on GitHub,
add your changes, and send me a pull request.
Source:
brainflush
|
How to use custom de...
lets say i change this app to days to...
How to display a JPG...
Is that posible to zoom in and drag p...
Making a custom Andr...
Cool - I will use it carefully i promise
How to display a JPG...
problem in image loading - i have exe...
Introducing Calculon...
Thank You for your contribution - Hi,...