Android JUnit Testing



This is an example of how to setup Android JUnit testing. What we have here is a project to be tested (TestDemo) as well as the project that will run the tests on it (TestDemoTest). In other words, TestDemo is a testee and TestDemoTest is a tester code.

TestDemo is a simple weight converter converting from metric kilograms to imperial pounds and vice versa.

TestDemo - Weight Converter Application

Let's explain the testee code first - a simple weight converter application. It consists for a single activity that has two text input fields with listeners for user typing in them.

res/layout/main.xml

The user interface is fairly simple, with a single LinearLayout, couple of TextViews and couple of EditText fields. We only care about the EditText fields in this case.

Code:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_gravity="center">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="@string/titleConverter"
android:gravity="center" android:textSize="20sp"
android:layout_margin="20dp" />
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="@string/titleKilos"
android:textSize="20sp"></TextView>
<EditText android:layout_height="wrap_content" android:hint="@string/titleKilos"
android:id="@+id/editKilos" android:layout_width="fill_parent"></EditText>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="@string/titlePounds"
android:textSize="20sp"></TextView>
<EditText android:layout_height="wrap_content" android:id="@+id/editPounds"
android:hint="@string/titlePounds" android:layout_width="fill_parent"></EditText>
</LinearLayout>

src/com/marakana/TestDemo.java

The Java class represents our one and only activity. It only overrides onCreate() method where we find the two EditText fields we care about, and assign the OnKeyListeners to listen when the user types something in the field. We then try to convert from one unit to another by parsing the input and multiplying it with a conversion number. As the number parsing may fail, we catch those exceptions and report it as an error.

Code:

package com.marakana;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.EditText;

public class TestDemo extends Activity {
static final String TAG = "TestDemo";
EditText editKilos, editPounds;
public static final String ERROR = "ERROR";

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// Find views
editKilos = (EditText) findViewById(R.id.editKilos);
editPounds = (EditText) findViewById(R.id.editPounds);

// Setup listener for Kilos to Pounds
editKilos.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_UP)
return false;
try {
Log.d(TAG, String
.format("Kilos: %s", editKilos.getText().toString()));
double kilos = Double.parseDouble(editKilos.getText().toString());
double pounds = kilos * 2.20462262;
editPounds.setText(new Double(pounds).toString());
} catch (NumberFormatException e) {
editPounds.setText(ERROR);
Log.e(TAG, "e:" + e);
}
return true;
}
});

// Setup listener for Pounds to Kilos
editPounds.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_UP)
return false;
try {
Log.d(TAG, String.format("Pounds: %s", editPounds.getText()
.toString()));
double pounds = Double.parseDouble(editPounds.getText().toString());
double kilos = pounds * 0.45359237;
editKilos.setText(new Double(kilos).toString());
} catch (NumberFormatException e) {
editKilos.setText(ERROR);
Log.e(TAG, "e:" + e);
}
return true;
}
});

}
}

>Output

The output of this stand-alone application looks like this:

Source Code
https://www.protechtraining.com/static/tutorials/TestDemo.zip



TestDemoTest

Now let's look at the application that will test our TestDemo weight converter. This is a separate project, but it is set to be dependent on the TestDemo project since it needs to be able to load its classes. Notice also that we are using different Java packages for the two applications, to further separate them. TestDemo code lives in com.marakana package while TestDemoTest is in com.marakana.test package.

In Eclipse, to setup dependency of one project on another, open up TestDemoTest's project properties (right-click on project), select Java Build Path, click on Project tab, and select Add... to add the TestDemo project.

src/com/marakana/test/TestDemoTests.java

Just like in JUnit, you organize your test code in test cases, each typically a Java class. A test case consists of multiple unit tests, essentially Java methods. These tests either pass or fail by making certain assertions. You can also set up and tear down a test case.

Code:

package com.marakana.test;

import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.test.ViewAsserts;
import android.test.suitebuilder.annotation.SmallTest;
import android.widget.EditText;

import com.marakana.TestDemo;

/*
* Test code to test com.marakana.TestDemo
*
* To run on command line:
* adb -e shell am instrument -w -e class com.marakana.test.TestDemoTests
* com.marakana.test/android.test.InstrumentationTestRunner
*/
public class TestDemoTests extends ActivityInstrumentationTestCase2<TestDemo> {
EditText editKilos, editPounds;
TestDemo activity;

public TestDemoTests(String name) {
super("com.marakana", TestDemo.class);
setName(name);
}

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

// Find views
activity = getActivity();
editKilos = (EditText)activity.findViewById(com.marakana.R.id.editKilos);
editPounds = (EditText)activity.findViewById(com.marakana.R.id.editPounds);
}

protected void tearDown() throws Exception {
super.tearDown();
}

@SmallTest
public void testViewsCreated() {
assertNotNull(getActivity());
assertNotNull(editKilos);
assertNotNull(editPounds);
}

@SmallTest
public void testViewsVisible() {
ViewAsserts.assertOnScreen(editKilos.getRootView(), editPounds);
ViewAsserts.assertOnScreen(editPounds.getRootView(), editKilos);
}

@SmallTest
public void testStartingEmpty() {
assertTrue("Kilos field is empty", "".equals(editKilos.getText().toString()));
assertTrue("Pounds field is empty", "".equals(editPounds.getText().toString()));
}

@SmallTest
public void testKilosToPounds() {
editKilos.clearComposingText();
editPounds.clearComposingText();

TouchUtils.tapView(this, editKilos);
sendKeys("1");

double pounds;
try {
pounds = Double.parseDouble(editPounds.getText().toString());
} catch (NumberFormatException e) {
pounds = -1;
}
assertTrue("1 kilo is 2.20462262 pounds", pounds > 2.2 && pounds < 2.3);
}

}

What we have here is slightly different than a regular Java JUnit test case. For one, we subclass ActivityInstrumentationTestCase2 instead of standard TestCase. This is because our code is testing an Activity and ActivityInstrumentationTestCase2 knows a bit more about activities.

Notice that our setup code gets the activity to be tested, in this case TestDemo activity. We also find all the views we care about in this activity so we can simulate user input. In this case, we find the two EditText fields for kilos and pounds.

What follows is a set of simple tests. Each test needs to either pass or fail. That is done via calling appropriate asset. Most assert methods are straight-forward from JUnit, but Android adds a few Android-specific ones. Notice the use of ViewAsserts, for instance. Also, notice the use of TouchUtils to send key strokes to the activity.

When you have your projects setup properly, you can run the TestDemoTest code as Android JUnit Test. Select your TestDemoTest project, right click and choose Run As -> Android JUnit Test. This should open another view and show you the execution of the code right in Eclipse. The output would look like this:

Output

Source Code
https://www.protechtraining.com/static/tutorials/TestDemoTest.zip

Notice that you can also run Android JUnit tests from command line as well. This helps with automation of testing. The typical command would look like:

Code:
$ adb -e shell am instrument -w -e class com.marakana.test.TestDemoTests com.marakana.test/andr
oid.test.InstrumentationTestRunner

And the output would be something like this:

Code:

com.marakana.test.TestDemoTests:....
Test results for InstrumentationTestRunner=....
Time: 5.716

OK (4 tests)
Published May 17, 2010