Android Platform Development Kit

Introduction

Sometimes you may want to manually interact with your applications to verify that a particular feature or behavior is working properly; why not automate this verification with a JUnit TestCase that can instrument applications?

Instrumentation testing allows you to verify a particular feature or behavior with an automated JUnit TestCase. You can launch activities and providers within an application, send key events, and make assertions about various UI elements.

Classes

The following classes help glue together Instrumentation with JUnit testing.

Class Description
InstrumentationTestCase

This extends the standard JUnit TestCase and offers access to an Instrumentation class. Write tests inside your instrumentation class any way you see fit. For example, your test might launch activities and send key events. For this to work properly, the instrumentation needs to be injected into the test case.

InstrumentationTestRunner The instrumentation test runner is an instrumentation that runs instrumentation test cases and injects itself into each test case. Instrumentation test cases need to be grouped together with an instrumentation test runner with the appropriate target package.
InstrumentationTestSuite The instrumentation test suite is a simple extension of the standard JUnit TestSuite that keeps a member Instrumentation variable on hand to inject into each TestCase before running them. It is used by InstrumentationTestRunner.

Three additional base classes extend InstrumentationTestCase to allow you to test Activity and Provider classes:

Class Description
ActivityTestCase

This class can be used to write tests for a specific activity. An activity is launched in its setUp() method and finished with tearDown. If you write a test case that extends ActivityTestCase, you can write tests that access the activity using getActivity() and assume it has been set up properly.

SingleLaunchActivityTestCase This class is similar to ActivityTestCase except that the activity is launched once per class instead of every time the test case calls setup.
ProviderTestCase This class is similar to ActivityTestCase except that it will setup, tear down, and provide access to the Provider of your choice.

Running Tests

To run your tests, use the am instrument command with your InstrumentationTestRunner as its argument. Results are printed as a result of the instrumentation. For example, the following snippet displays the output after running the framework tests with one test failing (note the unusual syntax caused by how instrumentations are run via am):

$ adb shell am instrument -w com.google.android.frameworktest/.tests.FrameworkInstrumentationTestRunner
INSTRUMENTATION_RESULT: test results:=.......F.......
Time: 6.837
There was 1 failure:
1) testSetUpConditions(com.google.android.frameworktest.tests.focus.RequestFocusTest)junit.framework.AssertionFailedError: requestFocus() should work from onCreate.
        at com.google.android.frameworktest.tests.focus.RequestFocusTest.testSetUpConditions(RequestFocusTest.java:66)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at android.test.InstrumentationTestSuite.runTest(InstrumentationTestSuite.java:73)
        at android.test.InstrumentationTestSuite.runTest(InstrumentationTestSuite.java:73)
        at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:151)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1088)

FAILURES!!!
Tests run: 14,  Failures: 1,  Errors: 0

<RETURN> to continue

INSTRUMENTATION_CODE: -1
$ 

All Tests

$ adb shell am instrument -w MyInstrumentationTestRunner

A Single Test Case

$ adb shell am instrument \
     -e class MyInstrumentationTestCase \
     -w MyInstrumentationTestRunner

A Single Test

$ adb shell am instrument \
     -e class MyInstrumentationTestCase#myTestMethod \
     -w MyInstrumentationTestRunner

Creating Tests

New Instrumentation TestRunner

Create a class that derives from this class. You must override two abstract methods; one that returns the class loader of the target package, and another that defines all of the tests within the package. For example, the snippet below displays the test runner for the framework tests.

public class FrameworkInstrumentationTestRunner extends InstrumentationTestRunner {

    @Override
    public TestSuite getAllTests() {
        InstrumentationTestSuite suite = new InstrumentationTestSuite(this);

        suite.addTestSuite(FocusAfterRemovalTest.class);
        suite.addTestSuite(RequestFocusTest.class);
        suite.addTestSuite(RequestRectangleVisibleTest.class);
        return suite;
    }

    @Override
    public ClassLoader getLoader() {
        return FrameworkInstrumentationTestRunner.class.getClassLoader();
    }
}

Next, in an appropriate AndroidManifest.xml, define the instrumentation for the derived class with the appropriate android:targetPackage set. For example, the snippet below defines the instrumentation runner for the framework tests.

<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />

<instrumentation android:name="android.tests.FrameworkInstrumentationTestRunner"
                 android:targetPackage="com.google.android.frameworktest"
                 android:label="framework instrumentation test runner" />

New InstrumentationTestCase

To create a new test case, write a class that extends InstrumentationTestCase in the same application as your test runner. The following snippet illustrates an example ActivityTestCase that tests an activity named MyActivity.

public class ButtonPressTest extends ActivityTestCase<MyActivity> {

    Button mLeftButton;

    public ButtonPressTest() {
        super("com.example", MyActivity.class);
    }

    @Override
    public void setUp() throws Exception {
      super.setUp();
      mLeftButton = (Button) getActivity().findViewById(R.id.leftButton);
    }

    public void testFocusMovesToRight() throws Exception {
        assertTrue(mLeftButton.hasFocus());
        getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_DPAD_RIGHT);

        Button rightButton = (Button) getActivity().findViewById(R.id.rightButton);
        assertTrue(rightButton.hasFocus());
    }

    // could have several more tests...
}

Aliases for Running Framework Instrumentation Tests

# compiles and installs FrameworkTests
alias deploytests="(cd tests/FrameworkTests/ && mm) && adb install out/target/product/dream/system/app/FrameworkTest.apk"

# runs all of FrameworkTests unit tests
alias runtests="adb shell am instrument -w com.google.android.frameworktest/.tests.FrameworkInstrumentationTestRunner"

# runtest TEST: runs a single unit test, for instance runtest view.VisibilityTest
# -- for convenience, you don't have to type the com.google.android.frameworktest.tests.
function runtest {
    adb shell am instrument -e class com.google.android.frameworktest.tests.$1 -w com.google.android.frameworktest/.tests.FrameworkInstrumentationTestRunner
}

# debugtest TEST: runs a single unit test in debug mode, for instance runtest view.VisibilityTest
function debugtest {
    adb shell am instrument -e debug true -e class com.google.android.frameworktest.tests.$1 -w com.google.android.frameworktest/.tests.FrameworkInstrumentationTestRunner
}

v0.3 - 9 June 2008