Up and running with TDD on Android

A couple of weeks ago, I happened to be in the right place at the right time (the first ORD Session) when Google hooked a bunch of developers up with an Android Dev Phone 1.

I’d been interested in Android for awhile because it’s a more open platform than the iPhone, and the code is Java (I’ve never worked in Objective-C), so I was excited.

After my initial excitement of hacking around and getting things to work, I decided to regain my discipline and figure out a workflow for TDD. The good news is that JUnit is built right into the Java framework that Android app have available.

The less-good news is that writing and running tests on Android isn’t as well-documented as many other facets of the application framework. I’d like to share my setup, which doesn’t depend on any specific IDE or text editor.

I’m assuming Mac or Linux here, but I’m sure Windows would only require minimal changes. Other assumptions:

  • The Android SDK is installed
  • Android tools directory is on the PATH
  • Ant is on the machine and on the PATH

Note: The tools directory is the main one, not the one below platforms. In my case: /Users/colin/lib/android/tools.

Let’s get started by creating a project with the command-line utility, which will give you the test directory structure and build files that you need (you won’t get these with the Eclipse or IntelliJ plugins, for instance).

Of course, you can read documentation for the android command to see more details, but here’s what we’re doing:

1 $ android create project -t 1 -p tictactoe-android -a TicTacToe -k com.colinwjones
2 $ cd tictactoe-android

All of these options are required (-n, the project name, is optional, and I’ve left it out above)

  • -t is the build target platform
  • -p is the path of the new project to create
  • -a is the first Activity that you want to create
  • -k is your package name

Next, we want to create an AVD if one doesn’t already exist—it’s an emulator for the phone operating system. We can check to see if one exists already first:

1 $ android list avd

If you don’t have an AVD already, create one:

1 $ android create avd -n ColinPhone -t 1

You’ll be asked if you want a custom hardware profile (if you don’t, just hit Return).

  • -n is the name of the new device
  • -t is the target platform, as before

At this point, let’s go ahead and fire up the emulator, making sure to match the name to the one you created. It’s good to make sure that our state at this early point is a good one.

1 $ emulator -avd ColinPhone &

Here I’m launching the emulator (AVD) in the background so I don’t need to open up a new Terminal window. You’ll get some output in your terminal window; if it bothers you, Ctrl-L will freshen it up.

Now we’ll build and install both of your apps (the test package is really a separate application, which is a good idea to keep the tests out of the eventual deployment package anyway):

1 $ ant debug
2 $ adb install -r bin/TicTacToe-debug.apk
3 $ cd tests
4 $ ant debug
5 $ adb install -r bin/TicTacToe-debug.apk

The debug target isn’t actually defined in either buildfile (build.xml or tests/build.xml), but is available nonetheless (see $ ant help for other targets you might not otherwise find). This takes care of bundling resources, compiling, converting classfiles to Android’s .dex format, and packaging.

Note that adb is not ant—it’s the Android Debug Bridge, and it’s invaluable for working with the emulator. The -r option to adb install reinstalls the package if necessary. Now, this is pretty redundant-looking, but just remember that the tests directory is a sort of parallel structure with your project directory, and you need both.

It’s time to run the default test suite that the android create project call has given us (this can be run either from tests directory or from the root of the project):

1 $ adb shell am instrument -w com.colinwjones.tests/android.test.InstrumentationTestRunner

Of course, you’ll want to substitute your package and activity names for the ones in my examples. It’s very important to realize that you’ve just compiled and installed your software on the emulator and are running tests on it there.

In order to do TDD, you’ll need to recompile changed code and reinstall on the emulator. I hope that one day there will be a way to avoid going through the emulator each time, but this is the only method I’ve been able to get working so far.

Now we need to actually add a real test to tests/src/com/colinwjones/TicTacToeTest.java. Here, an IDE like IntelliJ or Eclipse comes in handy, especially if you’re just starting with Android and aren’t sure of the methods you might want to use.

 1 // with imports at top:
 2 
 3 import import android.widget.Button;
 4 
 5 /* some code
 6  * ...
 7  * ...
 8  */
 9 
10 // inside your test class:
11 
12 public void testNewGameButtonExists() throws InterruptedException
13 {
14     Button button = (Button) getActivity().findViewById(R.id.new_button);
15     assertEquals("New Game", button.getText());

Building our test package at this point will fail, since no resource with the new_button id exists yet. Let’s do it anyway to see the first failure and guide us to our implementation code (running this from the tests directory):

1 $ ant debug

The error tells us where to go next: we’ll implement the button in /layout/main.xml (making sure to set the right ID on the button).

1 <Button android:layout_width="fill_parent"
2         android:layout_height="wrap_content"
3         android:id="@+id/new_button" />

Since we changed the main layout, the implementation package needs to be built first, then the test package:

1 $ cd ..
2 $ ant debug && adb install -r bin/TicTacToe-debug.apk
3 $ cd tests
4 $ ant debug && adb install -r bin/TicTacToe-debug.apk

Now run the tests again:

1 $ adb shell am instrument -w com.colinwjones.tests/android.test.InstrumentationTestRunner

Great! Now we have a proper failure:

1 com.colinwjones.TicTacToeTest:
2 Failure in testNewGameButtonExists:
3 junit.framework.AssertionFailedError: expected:<new game> but was:<>

We just need to add the right text in the XML layout:

1 <Button android:layout_width="fill_parent"
2         android:layout_height="wrap_content"
3         android:id="@+id/new_button"
4         android:text="New Game" />

Let’s rid of the default “Hello, World” TextView in main.xml while we’re at it. Now rebuild, reinstall, and re-run tests:

1 $ cd ..
2 $ ant debug && adb install -r bin/TicTacToe-debug.apk
3 $ cd tests
4 $ ant debug && adb install -r bin/TicTacToe-debug.apk
5 $ adb shell am instrument -w com.colinwjones.tests/android.test.InstrumentationTestRunner

This is getting annoying typing all these commands: we’re going to want to write a shell script or Ant task to do this for us. But for the time being, we’ll plod through (that was the last time, though). Now we’re passing:

1 Test results for InstrumentationTestRunner=..
2 Time: 1.009
3 OK (2 tests)

It’s a bit strange that the test runner claims we have 2 tests: each test class will add one of its own.

Now that we’ve seen how to get the tests running, let’s automate it by adding Ant tasks to the build.xml in the main project directory.

You’ll need to set the environment variable ANDROID_TOOLS for this exact task to work, or you can provide the full path to adb:

 1 <target name="clean">
 2   <delete includeemptydirs="true">
 3     <fileset dir="bin" includes="**/*" />
 4     <fileset dir="tests/bin" includes="**/*" />
 5   </delete>
 6 </target>
 7 <target depends="clean, debug" name="test">
 8   <property environment="env" />
 9   <property name="android-tools" value="${env.ANDROID_TOOLS}" />
10   <ant dir="tests" antfile="build.xml" inheritall="false" target="debug" />
11   <exec executable="${android-tools}/adb" failonerror="true">
12     <arg line="install -r bin/TicTacToe-debug.apk" />
13   </exec>
14   <exec executable="${android-tools}/adb" failonerror="true">
15     <arg line="install -r tests/bin/TicTacToe-debug.apk" />
16   </exec>
17   <exec executable="${android-tools}/adb">
18     <arg line="shell am instrument" />
19     <arg value="-w" />
20     <arg line="com.colinwjones.tests/android.test.InstrumentationTestRunner" />
21   </exec>
22 </target>

Now we can just run:

1 $ ant test

from the project directory (assuming the emulator is already up and running with emulator -avd ColinPhone &), and we’ll be good to go.

Honestly, it’s a pretty simple process: the key for me was in using the Android command-line tools rather than IDE plugins. It helped me to understand the build process and get beyond the initial frustration of not having the IDE do the work for me.

I imagine things will change a bit for Windows users, so please leave comments if there’s anything drastically different, or if things change for Mac/Linux users as the framework develops.

I do hope this will help someone else to get set up and save the headache I had when first discovering Android.

Colin Jones, Director of Software Services

Colin Jones is particularly interested in web security and functional programming, using languages like Clojure.