Tuesday, March 11, 2008

Test Driven Development and GUI Testing on the Android platform: Temperature Converter sample revisited

Test Driven Development and GUI Testing on the Android platform: Temperature Converter sample revisited

This document is published at http://docs.google.com/Doc?id=ddwc44gs_41dhjhbdd2

WARNING: Blogspot engine change the link, so you have to cut and paste.

Functional tests

The new Google Android SDK provides some improvements to the Instrumentation code and the ways of handling it. Using Dev Tools, now you can find and Instrumentation list entry that shows all of the classes providing Instrumentation and a way to run it.


We will be using positron positron-0.8-alpha and we will introduce one small change. Positron only presents the test results in the DDMS perspective

INFO/Positron(16414): .F..F...
INFO/Positron(16414): Time: 6.46
INFO/Positron(16414): There were 2 failures:

the idea is to use an Intent to be able to present the results as an activity: AndroidTestResults.
AndroidTestResults listen to the Intent broadcasted by Positron.
First, let's introduce this changes to the base Positron
  1. we have to define the intent
  2. using a Template Method pattern with a method called doHandleResult() implement invariant and variable parts of the algorithm
  3. define a default donHandleResult() to do nothing

public abstract class Positron extends Instrumentation { public static final String VIEW_TEST_RESULTS_ACTION = "positron.action.VIEW_TEST_RESULTS"; ... @Override public void onStart() { backup(); ByteArrayOutputStream testOutput = new ByteArrayOutputStream(1024); InstrumentedTestRunner runner = new InstrumentedTestRunner(this, new PrintStream(testOutput)); InstrumentedTestResult result = (InstrumentedTestResult)runner.doRun(suite()); Log.i(TAG, testOutput.toString()); // this implements a TEMPLATE METHOD pattern // by DTM doHandleResult(result); finishAll(); pauseButton.quit(); restore(); waitForIdleSync(); finish(result.errorCount() == 0 && result.failureCount() == 0 ? 0 : 1, result.toBundle()); } ... /** handle the result in the desired way */ protected void doHandleResult(InstrumentedTestResult result) { // do nothing } }
doHandleResult gives us the ability to handle the results in the way we want.

As part of the instrumentation of TemperatureConverter tests we have implemnented a class Positron that extends positron.Positron. In this class we need to implement the abstract method suite() returning our TestSuite, but now we are also adding our Template Method.

@Override protected void doHandleResult(InstrumentedTestResult result) { Intent intent = new Intent(VIEW_TEST_RESULTS_ACTION); intent.putExtras(result.toBundle()); intent.putExtra("suite", suite().getName()); getContext().broadcastIntent(intent); }


Let's change our OverallTest
package com.codtech.android.tdd.validation;
import java.io.IOException; import android.content.Intent; import com.codtech.android.tdd.R; import com.codtech.android.tdd.TemperatureConverterActivity;
/** * @author diego * */ public class OverallTest extends positron.TestCase { private static final String TAG = "OverallTests"; private TemperatureConverterActivity activity; /** * @param name */ public OverallTest(String name) { super(name); } /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); Intent intent = new Intent(getTargetContext(), TemperatureConverterActivity.class); startActivity(intent.addLaunchFlags(Intent.NEW_TASK_LAUNCH)); activity = (TemperatureConverterActivity)activity(); // Is it our application ? assertEquals(getTargetContext().getString(R.string.app_name), activity().getTitle()); // Do we have focus ? assertEquals(activity.getCelsiusEditText(), activity.getCurrentFocus()); } /* (non-Javadoc) * @see junit.framework.TestCase#tearDown() */ protected void tearDown() throws Exception { finishAll(); } public void testConversion() { // Enter a temperature sendString("123"); // Convert press(DOWN, DOWN, CENTER); // Why 2 downs are needed ? // Verify correct conversion 123C -> 253F assertEquals("valid conversion", "253", activity.getFahrenheitEditText().getText().toString()); } public void testValidCharachters() { // Enter invalid characters (only digits and +/- are allowed String str = "-0123456789"; sendString(str); assertEquals("invalid characters", str, activity.getCelsiusEditText().getText().toString()); } public void testInvalidCharachters() { // Enter invalid characters (only digits and +/- are allowed // in this "digits" mode, some keys are automatically converted into its // numeric keypad equivalentes (i=>-, o=>+, p=>+, etc.) sendString("abcdefghpqrstuvwxyz"); assertEquals("invalid characters", "", activity.getCelsiusEditText().getText().toString()); } public void testInvalidConversion() { // Enter a temperature sendString("-274"); // Convert press(DOWN, DOWN, CENTER); // Why 2 downs are needed ? // Verify that the wrong value is still displayed assertEquals("invalid conversion", "-274", activity.getCelsiusEditText().getText().toString()); // FIXME // there's still no way to determine if the dialog is showed //assertTrue("exception raised and handled", activity.getFahrenheitEditText().getText().toString().contains("below absolute zero")); } public static void main(String[] args) { try { positron.RunTests.main(new String[] {"com.codtech.android.tdd", ".Positron", "/opt/java/android-sdk"}); } catch (IOException e) { e.printStackTrace(); } } }

Now, we can run our Acceptance Test just running OverallTest as a Java Application (right click, RunAs -> Java Application).
In order for this to work we need TemperatureConverter installed into the emulator and the emulator running.
The Instrumentation will run, and just before finishing, Positron will be broadcasting and intent ("positron.action.VIEW_TEST_RESULTS")which is then received by an IntentReceiver which in turn start an activity.

Here, there are tow videos that show the process of launching the tests from Dev Tools.


Post a Comment