Wednesday, May 18, 2011

Robotium rocks!

Today, we're going to add some functionality to test the entry of invalid values. Let's get busy.

First, I'll make a copy of an existing test using ActivityInstrumentationTestCase2. I know it's kind of slow, but I'm going to be accessing resources and I think I need it for that. Actually, I'm not sure. Let's try accessing a resource from a test that uses ActivityUnitTestCase. I'm having trouble with deciding. I think I just need to play it safe and follow along with the examples from Google. What were those? Oh, yeah, it was Spinner. Let's take a look at some of its code:


@Override
protected void setUp() throws Exception {
super.setUp();

setActivityInitialTouchMode(false);

mActivity = getActivity();

mSpinner = (Spinner) mActivity
.findViewById(com.android.example.spinner.R.id.Spinner01);

mPlanetData = mSpinner.getAdapter();

} // end of setUp() method definition



In the setup, it's grabbing the control it's intending to test, plus it's adapter.

// this is run only once at the start of the app,
// to make sure the project is initialized correctly

public void testPreConditions() {
assertTrue(mSpinner.getOnItemSelectedListener() != null);
assertTrue(mPlanetData != null);
assertEquals(mPlanetData.getCount(), ADAPTER_COUNT);
} // end of testPreConditions() method definition

Here it's checking to make sure everything's been initialized.


public void testSpinnerUI() {

mActivity.runOnUiThread(
new Runnable() {
public void run() {
mSpinner.requestFocus();
mSpinner.setSelection(INITIAL_POSITION);
} // end of run() method definition
} // end of anonymous Runnable object instantiation
); // end of invocation of runOnUiThread

This looks like it creates a new thread. It sets the focus on the control and sets the selection on it. I remember reading something about how you need to test UI on the UT thread - but it was done as an annotation.

Here's the rest of the method:

this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
for (int i = 1; i <= TEST_POSITION; i++) {
this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
} // end of for loop

this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);

mPos = mSpinner.getSelectedItemPosition();
mSelection = (String)mSpinner.getItemAtPosition(mPos);
TextView resultView =
(TextView) mActivity.findViewById(
com.android.example.spinner.R.id.SpinnerResult
);

String resultText = (String) resultView.getText();

assertEquals(resultText,mSelection);

} // end of testSpinnerUI() method definition



Ok, let's model test activity based on spinner code. I also found another link,

http://mobile.tutsplus.com/tutorials/android/android-sdk-junit-testing/

which looks like a nice simple example.

We'll try something like this:

public void testSendKeys() {

mActivity.runOnUiThread(
new Runnable() {
public void run() {
mLevel.requestFocus();
//mLevel.setSelection(INITIAL_POSITION);
} // end of run() method definition
} // end of anonymous Runnable object instantiation
); // end of invocation of runOnUiThread

/// now on level
sendKeys(NUMBER_5);

// now on start quiz number
sendKeys(NUMBER_1);

// now on end quiz number
sendKeys(NUMBER_1000);

// save
sendKeys("ENTER");

Assert.assertEquals(true, true);
}

Ok, it crashed on an index out of bounds. I noticed it looked like it wasn't clearing the fields before it populated them. It was too fast. Let's unplug an run it on the emulator, which is much slower. Woah - way too slow. Back to the device.

Ok, after a little experimentation, this sequence gives me a passing test:

// / now on level
sendKeys(KeyEvent.KEYCODE_BACK);
sendKeys(KeyEvent.KEYCODE_DEL);

sendKeys(NUMBER_5);

// now on start quiz number
sendKeys(KeyEvent.KEYCODE_BACK);
sendKeys(KeyEvent.KEYCODE_DEL);
sendKeys(NUMBER_1);
// now on end quiz numbe
for (int i = 0; i < 5; i++) {
sendKeys(KeyEvent.KEYCODE_BACK);
}
for (int i = 0; i < 5; i++) {
sendKeys(KeyEvent.KEYCODE_DEL);
}

sendKeys(NUMBER_1000);

// save
sendKeys("ENTER");

Assert.assertEquals(true, true);


Now, let's see what happens when we put a crazy value in there somewhere.

sendKeys(NUMBER_9999);

9999 is a larger number then the available vocabulary limit.


I'm just having a lot of difficulty figuring out what's going on when the test runs; on the device, it goes by too fast, and sometimes the screen size changes. Also, it's not at clear it's entering the digits I want. Plus, I know it should be crashing, and it isn't. The emulator is incredibly slow.

So, I'm going to go to plan B and try Robotium. This is a frustrating thing. A morning's worth of work, pretty much trashed. This is the downside of TDD on Android. The tools are hard to work with.

Let's see, is there a tutorial or something?

Yeah. Ok, a youtube video. Ok, download the sample project. Ok, it needs notepad list. Ok, got that. Run the test. Ok, seems to work.

Let's make a copy of that project. Ok, don't forget to change the target package name in the manifest; and the package name. Add the target package to the projects tab in settings.

ok, let's try something like this:

solo.clickOnButton(0);

Wow, very cool. Not only did it press the correct button, it went to the next activity screen, without my having to specifically load it. Multi activities, something I hadn't even gotten close to with the Android testing framework.

I like the Robotium api - all the events are easy to pick out. It supports things like clear text.

Lets see if we can enter some text...

solo.clickOnButton(0);
solo.clearEditText(0);
solo.enterText(0, "17");

Ok, how to I save it?

solo.clickOnButton(0);
solo.clearEditText(0);
solo.enterText(0, "17");
solo.clickOnButton(0);

Index out of bounds - perfect! Amazing - this is so much easier than Android testing framework.

Ok, let's fix up the code that causes this problem. Suddenly, I'm back into production code. Actually - this should work for the lifecycle checks I had to comment out, which I went to such pains to create.

Let's fix index out of bounds.

Ouch. Again, adding test code to return a value is just a bad idea. Especially a hardcoded one. It was ok when I was building the function, but anything hardcoded has to be cleaned out pretty quickly. Let's comment out that code.

Pass.

The problem is, I now have tests to run from two separate projects. I wonder how many of the android junit test I could bring into this one. Either that, or just test everything functionally. I think there's some I couldn't really do that for, that really do test actual methods.

Well, that's good for now. Let's pick up some more of the great Robotium in the next post.

No comments:

Post a Comment