Thursday, February 16, 2012

Testing Android User Interfaces with Robotium


Overview

Android provides a 'test project' framework and various tools for testing apps.  Robotium is an opensource project which enhances these facilities.  Robotium provides convenience and utility methods which simplify clicking buttons, accessing text fields, etc.  Robotium hides the underlying syntax and procedures.

Tests can be run on emulators as well as real devices.

Tests can be started within eclipse as well as from a command-line.

Testcase methods can access localized message strings.

Setup

- Create the Android app to be tested (aka app-under-test).

- Create a basic Android Test Project associated with your app-under-test.  Follow basic Android instructions:
http://developer.android.com/resources/tutorials/testing/helloandroid_test.html

- Verify it runs.
From Eclipse package explorer, right-click the test project-> Run As-> Android JUnit Test.

- Fetch the Robotium jar file:
http://code.google.com/p/robotium/ -> Downloads -> robotium-solo-3.1.jar (or later)
  Optional: Fetch the javadoc.

- Add the Robotium jar file to the test project 'libs' directory
From Eclipse-> right click test project-> Properties-> Java build path-> Add external jars-> point to jar.

- In your testcase class, import the Robotium Solo class and define a reference to a Solo object:

    import com.jayway.android.robotium.solo.Solo;

    private Solo mSolo;

  Fix your dependencies and get your testcase class to compile.

- Instantiate a Solo object in one of your testcase methods:

        // Instantiate a new Solo object.
        mSolo = new Solo(getInstrumentation(), getActivity());

  Verify it runs.  You are now ready to make use of Robotium...

Using Robotium

Study the methods available in class Solo.  Either read the javadoc or browse the source:
https://github.com/jayway/robotium/tree/master/robotium-solo/src/main/java/com/jayway/android/robotium/solo

- Access widgets in your app-under-test using Solo.  Examples:

-- My app-under-test contains one ToggleButton.  Since there is only one, I can use the index '0' to query this ToggleButton:

    // Query whether the button is checked.
    boolean checked = mSolo.isToggleButtonChecked(0)

-- My ToggleButton text is defined as 'OFF' and 'ON'.  To click the ToggleButton, I use the name:

    // Click the OFF button to ON.
    mSolo.clickOnToggleButton("OFF");

    Note that the 'name' of a ToggleButton changes from 'OFF' to 'ON' depending upon its state.  To click it again when it is on, issue the following:

    // Click the ON button to OFF.
    mSolo.clickOnToggleButton("ON");

-- My app also has one TextView which displays various messages.  I can use Robotium Solo to query if a message is visible:

        // Query whether the message is displayed.
        boolean found = mSolo.searchText("green apples");

-- Robotium also has a set of methods named 'waitForText' with various parameters.  I look forward to using these.

Internationalization

Rich apps display text messages in various languages.  Testcases which search for text strings must be able to access the localized string.   The basic Android test project supports this, and it works with Robotium as well.

I added several strings to be translated in my app-under-test.  From res/values/strings.xml:
    <string name="on">ON</string>
    <string name="off">OFF</string>
    <string name="green_apples">green apples</string>

I internationalized my toggle button to use these values:
    <ToggleButton
        ...
        android:textOn="@string/on"
        android:textOff="@string/off"  >
    </ToggleButton>

Then within the test project...

    // Get localized strings.
    int idOff = com.example.app-under-test-package-name.R.string.off;
    String off = getActivity().getResources().getString(idOff);

    // Click the OFF button to ON.
    mSolo.clickOnToggleButton(off);

Similarly for text fields...

    int idGreenApples = com.example.app-under-test-package-name.R.string.green_apples;
    String greenApples = getActivity().getResources().getString(idGreenApples);

        // Query whether the message is displayed.
        boolean found = mSolo.searchText(greenApples);

Execution

There are two ways to invoke tests...

From eclipse package explorer, right-click the test project-> Run As-> Android JUnit Test.  Eclipse first installs the app-under-test APK and the testcase APK.  Then it switches to the familiar JUnit view which shows a list of tests as they run, along with a progress bar.

From a command-line, issue ADB commands to install both APKs, then issue an ADB command to start the testcase app (specifying your testcase package name and the Android InstrumentationTestRunner).  Example:

    # Install the app-under-test.
    adb install -r app-under-test.apk

    # Install the test app.
    adb install -r testcases.apk

    # Start the test app:
    adb shell am instrument -w  com.example.test-package-name/android.test.InstrumentationTestRunner

In both cases, you can watch the tests run on your emulator or real device.

Execution on multiple devices

If you have several real devices attached to your computer via USB cable, you can run all the tests in parallel, from different command-line windows (or through fancy scripting).  To do this, you must specify the device ID. First query the list of devices (obfuscated results):

    adb devices
    List of devices attached
    S84KD9G0D0F4    device
    P40D9GKKRKD8    device

Then target each ADB command to a specific device.  Example:

    adb -s S84KD9G0D0F4 install -r app-under-test.apk

Summary

Robotium provides a large set of convenience methods for accessing widgets and text fields, which makes your testcases smaller, simpler, and easier to understand.  Very nice.  Study the methods and make use of them.

References:  

Android Test Guide:  http://developer.android.com/guide/topics/testing/testing_android.html
Robotium:  http://robotium.org
Tutorials:  http://lmgtfy.com/?q=robotium+tutorial

Monday, February 6, 2012

How to use variables in Android internationalized message strings


This article explains how to include variables within internationalized message strings for Android applications.  It also explains how to specify an explicit locale (ie, language) for messages.

All secrets were gleaned from the references listed at bottom.

Prereqs

This article assumes you have:
- Set up the Android SDK and can build, install, and run an APK.
- Created a message file .../res/values/strings.xml
- Can access messages in the file using Context.getResources()...

Default locale messaging, no variables

In basic messaging, the simple Android method getString() may be used to get a message in the default locale.

In res/values/strings.xml:
    <string name="hello_message">Hello World</string>

In the java class:
    String message = myContext.getResources().getString(R.string.hello_message);

The resulting string 'message' is returned in the default locale:
    Hello World



Default locale messaging, 1 variable

To include a variable inside the message, Java formatting strings such as %d (int) and %s (string) may be included in strings.xml.  (See a full list of formatting strings in the references below.)

In res/values/strings.xml:
    <string name="ate_n_donuts">I ate %d donuts.</string>

In the java class:
    int numberDonuts = 12;
    String message = myContext.getResources().getString(R.string.ate_n_donuts, numberDonuts);

The resulting string 'message' is returned in the default locale:
    I ate 12 donuts.


Default locale messaging, multiple variables

Multiple variables in a string must be numbered with 1$, 2$, 3$.  These sequence numbers are embedded inside the formatting string, %s or %d, to appear as:  %1$s, %2$s, etc.  (very odd.)

Take note the sequence starts at 1, not zero.

The numbers correspond to the sequence of variables in the java method parameters.  This scheme allows translators to modify the sequence of variables to match the natural language style.  Example:

In res/values/strings.xml:
    <string name="game_results">The %1$s beat the %2$s in the %3$s.</string>

In your java class:
    String team1 = "New York Giants"
    String team2 = "New England Patriots"
    String gameName = "Super Bowl";
    String message = myContext.getResources().getString(R.string.game_results, team1, team2, gameName);

The resulting string 'message' is returned in the default locale:
    The New York Giants beat the New England Patriots in the Super Bowl.


Explicit locale messaging

The previous examples returned strings in the default locale of the mobile device automatically.  Messages displayed to the user should be presented this way.  However, for debug and servicability purposes, it may be useful to present messages in the language of the developer.  For example, I always want my log files in English.

To present a message in an explicit locale, the native Java formatter must be used.  In this last example, notice the variable is passed-in to the Java String.format() method, instead of to the Android getString() method.  Repeating the donut example...

In res/values/strings.xml:
    <string name="ate_n_donuts">I ate %d donuts.</string>

In the java class:
    int numberDonuts = 12;
    Locale logLocale = Locale.US;
    String messageEnglish = String.format(logLocale,
        myContext.getResources().getString(R.string.ate_n_donuts),
        numberDonuts);

The resulting string 'message' is returned in the explicitly-specified locale:
    I ate 12 donuts.


Really complex messaging

You now have all the secrets.  You can specify multiple variables in an internationalized message string.  You can get strings in the default locale and a specific locale.

And you can see that your code will get really complex and ugly fast.


Recommendations

I recommend you study the fine points in these examples.  Then write some helper methods to hide all these details from the main flow of your application.

Good luck!



Excellent References:

Bram's blog with comment from Romain Guy:
http://bvirtual.nl/2009/11/variable-in-androids-stringsxml.html

Official Android docs:
http://developer.android.com/reference/java/lang/String.html#format(java.lang.String, java.lang.Object...)
http://developer.android.com/reference/java/util/Formatter.html
http://developer.android.com/reference/android/content/Context.html#getString%28int,%20java.lang.Object...%29