Android Application Testing: Adding Functionality to the UI

Exclusive offer: get 50% off this eBook here
Android Application Testing Guide

Android Application Testing Guide — Save 50%

Build intensively tested and bug free Android applications

$26.99    $13.50
by Diego Torres Milano | June 2011 | Web Services

It doesn't matter how much time you invest in Android design, or even how careful you are when programming, mistakes are inevitable and bugs will appear.

The previous article by Diego Torres Milano, author of Android Application Testing Guide, introduced the Test Driven Development discipline.

In this article we will take a look at adding some basic functionality to the user interface.

 

Android Application Testing Guide

Android Application Testing Guide Build intensively tested and bug free Android applications
        Read more about this book      

(For more resources related to this subject, see here.)

The user interface is in place. Now we start adding some basic functionality.

This functionality will include the code to handle the actual temperature conversion.

Temperature conversion

From the list of requirements from the previous article we can obtain this statement: When one temperature is entered in one field the other one is automatically updated with the conversion.

Following our plan we must implement this as a test to verify that the correct functionality is there. Our test would look something like this:

@UiThreadTest
public final void testFahrenheitToCelsiusConversion() {
mCelsius.clear();
mFahrenheit.clear();
final double f = 32.5;
mFahrenheit.requestFocus();
mFahrenheit.setNumber(f);
mCelsius.requestFocus();
final double expectedC =
TemperatureConverter.fahrenheitToCelsius(f);
final double actualC = mCelsius.getNumber();
final double delta = Math.abs(expectedC - actualC);
final String msg = "" + f + "F -> " + expectedC + "C
but was " + actualC + "C (delta " + delta + ")";
assertTrue(msg, delta < 0.005);
}

Firstly, as we already know, to interact with the UI changing its values we should run the test on the UI thread and thus is annotated with @UiThreadTest.

Secondly, we are using a specialized class to replace EditText providing some convenience methods like clear() or setNumber(). This would improve our application design.

Next, we invoke a converter, named TemperatureConverter, a utility class providing the different methods to convert between different temperature units and using different types for the temperature values.

Finally, as we will be truncating the results to provide them in a suitable format presented in the user interface we should compare against a delta to assert the value of the conversion.

Creating the test as it is will force us to follow the planned path. Our first objective is to add the needed code to get the test to compile and then to satisfy the test's needs.

The EditNumber class

In our main project, not in the tests one, we should create the class EditNumber extending EditText as we need to extend its functionality.

We use Eclipse's help to create this class using File | New | Class or its shortcut in the Toolbars.

This screenshot shows the window that appears after using this shortcut:

Android Application Testing: Adding Functionality to the UI

The following table describes the most important fields and their meaning in the previous screen:

Field

Description

Source folder:

The source folder for the newly-created class. In this case the default location is fine.

Package:

The package where the new class is created. In this case the default package com.example.aatg.tc is fine too.

Name:

The name of the class. In this case we use EditNumber.

Modifiers:

Modifiers for the class. In this particular case we are creating a public class.

Superclass:

The superclass for the newly-created type. We are creating a custom View and extending the behavior of EditText, so this is precisely the class we select for the supertype.

Remember to use Browse... to find the correct package.

Which method stubs would you like to create?

These are the method stubs we want Eclipse to create for us. Selecting Constructors from superclass and Inherited abstract methods would be of great help.

As we are creating a custom View we should provide the constructors that are used in different situations, for example when the custom View is used inside an XML layout.

Do you want to add comments?

Some comments are added automatically when this option is selected.

You can configure Eclipse to personalize these comments.

Once the class is created we need to change the type of the fields first in our test:

public class TemperatureConverterActivityTests extends
ActivityInstrumentationTestCase2<TemperatureConverterActivity> {
private TemperatureConverterActivity mActivity;
private EditNumber mCelsius;
private EditNumber mFahrenheit;
private TextView mCelsiusLabel;
private TextView mFahrenheitLabel;
...

Then change any cast that is present in the tests. Eclipse will help you do that.

If everything goes well, there are still two problems we need to fix before being able to compile the test:

  • We still don't have the methods clear() and setNumber() in EditNumber
  • We don't have the TemperatureConverter utility class

To create the methods we are using Eclipse's helpful actions. Let's choose Create method clear() in type EditNumber.

Same for setNumber() and getNumber().

Finally, we must create the TemperatureConverter class.

Be sure to create it in the main project and not in the test project.

Android Application Testing: Adding Functionality to the UI

Having done this, in our test select Create method fahrenheitToCelsius in type TemperatureConverter.

This fixes our last problem and leads us to a test that we can now compile and run.

Surprisingly, or not, when we run the tests, they will fail with an exception:

09-06 13:22:36.927: INFO/TestRunner(348): java.lang.
ClassCastException: android.widget.EditText
09-06 13:22:36.927: INFO/TestRunner(348): at com.example.aatg.
tc.test.TemperatureConverterActivityTests.setUp(
TemperatureConverterActivityTests.java:41)
09-06 13:22:36.927: INFO/TestRunner(348): at junit.framework.
TestCase.runBare(TestCase.java:125)

That is because we updated all of our Java files to include our newly-created EditNumber class but forgot to change the XMLs, and this could only be detected at runtime.

Let's proceed to update our UI definition:

<com.example.aatg.tc.EditNumber
android:layout_height="wrap_content"
android:id="@+id/celsius"
android:layout_width="match_parent"
android:layout_margin="@dimen/margin"
android:gravity="right|center_vertical"

android:saveEnabled="true" />

That is, we replace the original EditText by com.example.aatg.tc.EditNumber which is a View extending the original EditText.

Now we run the tests again and we discover that all tests pass.

But wait a minute, we haven't implemented any conversion or any handling of values in the new EditNumber class and all tests passed with no problem. Yes, they passed because we don't have enough restrictions in our system and the ones in place simply cancel themselves.

Before going further, let's analyze what just happened. Our test invoked the mFahrenheit.setNumber(f) method to set the temperature entered in the Fahrenheit field, but setNumber() is not implemented and it is an empty method as generated by Eclipse and does nothing at all. So the field remains empty.

Next, the value for expectedC—the expected temperature in Celsius is calculated invoking TemperatureConverter.fahrenheitToCelsius(f), but this is also an empty method as generated by Eclipse. In this case, because Eclipse knows about the return type it returns a constant 0. So expectedC becomes 0.

Then the actual value for the conversion is obtained from the UI. In this case invoking getNumber() from EditNumber. But once again this method was automatically generated by Eclipse and to satisfy the restriction imposed by its signature, it must return a value that Eclipse fills with 0.

The delta value is again 0, as calculated by Math.abs(expectedC – actualC).

And finally our assertion assertTrue(msg, delta < 0.005) is true because delta=0 satisfies the condition, and the test passes.

So, is our methodology flawed as it cannot detect a simple situation like this?

No, not at all. The problem here is that we don't have enough restrictions and they are satisfied by the default values used by Eclipse to complete auto-generated methods. One alternative could be to throw exceptions at all of the auto-generated methods, something like RuntimeException("not yet implemented") to detect its use when not implemented. But we will be adding enough restrictions in our system to easily trap this condition.

TemperatureConverter unit tests

It seems, from our previous experience, that the default conversion implemented by Eclipse always returns 0, so we need something more robust. Otherwise this will be only returning a valid result when the parameter takes the value of 32F.

The TemperatureConverter is a utility class not related with the Android infrastructure, so a standard unit test will be enough to test it.

We create our tests using Eclipse's File | New | JUnit Test Case, filling in some appropriate values, and selecting the method to generate a test as shown in the next screenshot.

Firstly, we create the unit test by extending junit.framework.TestCase and selecting com.example.aatg.tc.TemperatureConverter as the class under test:

Android Application Testing: Adding Functionality to the UI

Then by pressing the Next > button we can obtain the list of methods we may want to test:

Android Application Testing: Adding Functionality to the UI

We have implemented only one method in TemperatureConverter, so it's the only one appearing in the list. Other classes implementing more methods will display all the options here.

It's good to note that even if the test method is auto-generated by Eclipse it won't pass. It will fail with the message Not yet implemented to remind us that something is missing.

Let's start by changing this:

/**
* Test method for {@link com.example.aatg.tc.
TemperatureConverter#fahrenheitToCelsius(double)}.
*/
public final void testFahrenheitToCelsius() {
for (double c: conversionTableDouble.keySet()) {
final double f = conversionTableDouble.get(c);
final double ca = TemperatureConverter.fahrenheitToCelsius(f);
final double delta = Math.abs(ca - c);
final String msg = "" + f + "F -> " + c + "C but is "
+ ca + " (delta " + delta + ")";
assertTrue(msg, delta < 0.0001);
}
}

Creating a conversion table with values for different temperature conversion we know from other sources would be a good way to drive this test.

private static final HashMap<Double, Double>
conversionTableDouble = new HashMap<Double, Double>();
static {
// initialize (c, f) pairs
conversionTableDouble.put(0.0, 32.0);
conversionTableDouble.put(100.0, 212.0);
conversionTableDouble.put(-1.0, 30.20);
conversionTableDouble.put(-100.0, -148.0);
conversionTableDouble.put(32.0, 89.60);
conversionTableDouble.put(-40.0, -40.0);
conversionTableDouble.put(-273.0, -459.40);
}

We may just run this test to verify that it fails, giving us this trace:

junit.framework.AssertionFailedError: -40.0F -> -40.0C but is 0.0
(delta 40.0)at com.example.aatg.tc.test.TemperatureConverterTests.
testFahrenheitToCelsius(TemperatureConverterTests.java:62)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.
java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.
java:154)
at android.test.InstrumentationTestRunner.onStart(
InstrumentationTestRunner.java:520)
at android.app.Instrumentation$InstrumentationThread.run(
Instrumentation.java:1447)

Well, this was something we were expecting as our conversion always returns 0. Implementing our conversion, we discover that we need some ABSOLUTE_ZERO_F constant:

public class TemperatureConverter {
public static final double ABSOLUTE_ZERO_C = -273.15d;
public static final double ABSOLUTE_ZERO_F = -459.67d;
private static final String ERROR_MESSAGE_BELOW_ZERO_FMT =
"Invalid temperature: %.2f%c below absolute zero";
public static double fahrenheitToCelsius(double f) {
if (f < ABSOLUTE_ZERO_F) {
throw new InvalidTemperatureException(
String.format(ERROR_MESSAGE_BELOW_ZERO_FMT, f, 'F'));
}
return ((f - 32) / 1.8d);
}
}

Absolute zero is the theoretical temperature at which entropy would reach its minimum value. To be able to reach this absolute zero state, according to the laws of thermodynamics, the system should be isolated from the rest of the universe. Thus it is an unreachable state. However, by international agreement, absolute zero is defined as 0K on the Kelvin scale and as -273.15°C on the Celsius scale or to -459.67°F on the Fahrenheit scale.

We are creating a custom exception, InvalidTemperatureException, to indicate a failure providing a valid temperature to the conversion method. This exception is created simply by extending RuntimeException:

public class InvalidTemperatureException extends RuntimeException {
public InvalidTemperatureException(String msg) {
super(msg);
}
}

Running the tests again we now discover that testFahrenheitToCelsiusConversion test fails, however testFahrenheitToCelsius succeeds. This tells us that now conversions are correctly handled by the converter class but there are still some problems with the UI handling this conversion.

A closer look at the failure trace reveals that there's something still returning 0 when it shouldn't.

This reminds us that we are still lacking a proper EditNumber implementation. Before proceeding to implement the mentioned methods, let's create the corresponding tests to verify what we are implementing is correct.

Android Application Testing Guide Build intensively tested and bug free Android applications
Published: June 2011
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources related to this subject, see here.)

The EditNumber tests

The best base class for our custom View tests is AndroidTestCase, as we need a mock Context to create the custom View but we don't need system infrastructure.

This is the dialog we have to complete to create the tests. In this case using android. test.AndroidTestCase as the base class and com.example.aatg.tc.EditNumber as the class under test:

Android Application Testing: Adding Functionality to the UI

After pressing Next >, we select the methods for which stubs are created:

Android Application Testing: Adding Functionality to the UI

We need to update the auto-generated constructor to reflect the pattern we identified before, the given name pattern:

/**
* Constructor
*/
public EditNumberTests() {
this("EditNumberTests");
}
/**
* @param name
*/
public EditNumberTests(String name) {
setName(name);
}

The next step is to create the fixture. In this case this is a simple EditNumber which we will be testing:

/* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
mEditNumber = new EditNumber(mContext);
mEditNumber.setFocusable(true);
}

The mock context is obtained from the protected field mContext (http://developer.android.com/reference/android/test/AndroidTestCase. html#mContext) available in the AndroidTestCase class.

At the end of the test we set mEditNumber as a focusable View, that is it will be able to gain focus, as it will be participating in a bunch of tests simulating UIs that may need to request its focus explicitly.

Next, we test that the required clear() functionality is implemented correctly in the testClear() method:

/**
* Test method for {@link com.example.aatg.tc.EditNumber#clear()}.
*/
public final void testClear() {
final String value = "123.45";
mEditNumber.setText(value);
mEditNumber.clear();
String expectedString = "";
String actualString = mEditNumber.getText().toString();
assertEquals(expectedString, actualString);
}

Running the test we verify that it fails:

junit.framework.ComparisonFailure: expected:<> but was:<123.45>
at com.example.aatg.tc.test.EditNumberTests.testClear(
EditNumberTests.java:62)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.
java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.
java:154)
at android.test.InstrumentationTestRunner.onStart(
InstrumentationTestRunner.java:529)
at android.app.Instrumentation$InstrumentationThread.run(
Instrumentation.java:1447)

We need to implement EditNumber.clear() correctly.

This is a simple case, so just by adding this implementation to EditNumber we satisfy the test:

public void clear() {
setText("");
}

Run the test and proceed. Now let's complete the testSetNumber() implementation:

/**
* Test method for {@link
com.example.aatg.tc.EditNumber#setNumber(double)}.
*/
public final void testSetNumber() {
mEditNumber.setNumber(123.45);
final String expected = "123.45";
final String actual = mEditNumber.getText().toString();
assertEquals(expected, actual);
}

Which fails unless we implement EditNumber.setNumber(), similar to this implementation:

private static final String DEFAULT_FORMAT = "%.2f";
public void setNumber(double f) {super.setText(
String.format(DEFAULT_FORMAT, f));
}

We are using a constant, DEFAULT_FORMAT, to hold the desired format to convert the numbers. This can be later converted to a property that could also be specified in the xml layout definition of the field.

The same goes for the testGetNumber() and getNumber() pair:

/**
* Test method for {@link
com.example.aatg.tc.EditNumber#getNumber()}.
*/
public final void testGetNumber() {
mEditNumber.setNumber(123.45);
final double expected = 123.45;
final double actual = mEditNumber.getNumber();
assertEquals(expected, actual);
}

And:

public double getNumber() {
Log.d("EditNumber", "getNumber() returning value
of '" + getText().toString() + "'");
return Double.valueOf(getText().toString());
}

Surprisingly these tests succeed. But now there's a test that was passing that started to fail: testFahrenheitToCelsiusConversion(). The reason is that now that we have implemented EditNumber.setNumber() and EditNumber.getNumber() correctly, some values are returned differently and this test method was relying on spurious values.

This is a screenshot of the results obtained after running the tests:

Android Application Testing: Adding Functionality to the UI

If you closely analyze the case, you can discover where the problem is.

Got it?

Our test method is expecting the conversion to be realized automatically when the focus changes, as was specified in our list of requirements: when one temperature is entered in one field the other one is automatically updated with the conversion.

Remember, we don't have buttons or anything else to convert temperature values, so the conversion is expected to be done automatically once the values are entered.

This leads us again to the TemperatureConverterActivity and the way it handles the conversions.

The TemperatureChangeWatcher class

One way of implementing the required behavior of constantly updating the other temperature value once one has changed is through a TextWatcher. From the documentation we can understand that a TextWatcher is an object of a type that is attached to an Editable; its methods will be called when the text is changed (http://developer.android.com/intl/de/reference/android/text/TextWatcher.html).

It seems that is what we need.

We implement this class as an inner class of TemperatureConverterActivity. This is the screenshot of the New Java Class in Eclipse:

Android Application Testing: Adding Functionality to the UI

And this is our code after some additions to the recently created class.

/**
* Changes fields values when text changes applying the
corresponding method.
*
*/
public class TemperatureChangedWatcher implements TextWatcher {
private final EditNumber mSource;
private final EditNumber mDest;
private OP mOp;
/**
* @param mDest
* @param convert
* @throws NoSuchMethodException
* @throws SecurityException
*/
public TemperatureChangedWatcher(TemperatureConverter.OP op) {
if ( op == OP.C2F ) {
this.mSource = mCelsius;
this.mDest = mFahrenheit;
}
else {
this.mSource = mFahrenheit;
this.mDest = mCelsius;
}
this.mOp = op;
}
/* (non-Javadoc)
* @see android.text.TextWatcher#afterTextChanged(
android.text.Editable)
*/
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see android.text.TextWatcher#beforeTextChanged(
java.lang.CharSequence, int, int, int)
*/
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see android.text.TextWatcher#onTextChanged(
java.lang.CharSequence, int, int, int)
*/
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if (!mDest.hasWindowFocus() || mDest.hasFocus() || s == null )
{
return;
}
final String str = s.toString();
if ( "".equals(str) ) {
mDest.setText("");
return;
}
try {
final double temp = Double.parseDouble(str);
final double result = (mOp == OP.C2F) ?
TemperatureConverter.celsiusToFahrenheit(temp) :
TemperatureConverter.fahrenheitToCelsius(temp);
final String resultString = String.format("%.2f", result);
mDest.setNumber(result);
mDest.setSelection(resultString.length());
} catch (NumberFormatException e) {
// WARNING
// this is generated while a number is entered,
// for example just a '-'
// so we don't want to show the error
} catch (Exception e) {
mSource.setError("ERROR: " + e.getLocalizedMessage());
}
}
}

We implement extending TextWatcher and overriding the unimplemented methods.

Because we will be using the same TemperatureChangeWatcher implementation for both fields, Celsius and Fahrenheit, we keep a reference to the fields used as source and destination as well as the operation needed to update their values. To specify this operation we are introducing an enum to the TemperatureConverter class.

/**
* C2F: celsiusToFahrenheit
* F2C: fahrenheitToCelsius
*/
public static enum OP { C2F, F2C };

This operation is specified in the constructor and the destination and source EditNumber are selected accordingly. This way we can use the same watcher for different conversions.

The method of the TextWatcher interface we are mainly interested in is onTextChanged, that will be called any time the text changes. At the beginning we avoid potential loops, checking who has focus and returning if the conditions are not met.

We also set the destination field as an empty String if the source is empty.

Finally, we try to set the resulting value of invoking the corresponding conversion method to set the destination field. We flag the error as necessary, avoiding showing premature errors when the conversion was invoked with a partially entered number.

We need to set the listener on the fields in TemperatureConverterActivity. onCreate():

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mCelsius = (EditNumber) findViewById(R.id.celsius);
mFahrenheit = (EditNumber) findViewById(R.id.fahrenheit);
mCelsius.addTextChangedListener(
new TemperatureChangedWatcher(OP.C2F));
mFahrenheit.addTextChangedListener(
new TemperatureChangedWatcher(OP.F2C));
}

To be able to run the tests we should compile them. To compile we need at least to define the celsiusToFahrenheit, which is not yet defined.

More TemperatureConverter tests

We need to implement celsiusToFahrenheit and as usual we start from the test.

This is fairly equivalent to the other conversion method fahrenheitToCelsius and we can use the infrastructure we devised while creating this test:

/**
* Test method for {@link com.example.aatg.tc.
TemperatureConverter#celsiusToFahrenheit(double)}.
*/
public final void testCelsiusToFahrenheit() {
for (double c: conversionTableDouble.keySet()) {
final double f = conversionTableDouble.get(c);
final double fa = TemperatureConverter.
celsiusToFahrenheit(c);
final double delta = Math.abs(fa - f);
final String msg = "" + c + "C -> " + f + "F but is " + fa +
" (delta " + delta + ")";
assertTrue(msg, delta < 0.0001);
}
}

We use the conversion table to exercise the method through different conversions and we verify that the error is less than a predefined delta.

Then, the correspondent conversion implementation in TemperatureConverter class is:

public static double celsiusToFahrenheit(double c) {
if (c < ABSOLUTE_ZERO_C) {
throw new InvalidTemperatureException(
String.format(ERROR_MESSAGE_BELOW_ZERO_FMT, c, 'C'));
}
return (c * 1.8d + 32);
}

Now all the tests are passing but we are still not testing all the common conditions. You should check if errors and exceptions are correctly generated, besides all the normal cases we created so far.

This is the test we create to check the correct generation of exceptions when a temperature below absolute zero is used in a conversion:

public final void testExceptionForLessThanAbsoluteZeroF() {
try {
TemperatureConverter.fahrenheitToCelsius(
TemperatureConverter.ABSOLUTE_ZERO_F-1);
fail();
}
catch (InvalidTemperatureException ex) {
// do nothing
}
}

In this test we decrement the absolute zero temperature to obtain an even smaller value and then we attempt the conversion. We check for the correct exception being caught and finally we assert this condition:

public final void testExceptionForLessThanAbsoluteZeroC() {
try {
TemperatureConverter.celsiusToFahrenheit(
TemperatureConverter.ABSOLUTE_ZERO_C-1);
fail();
}
catch (InvalidTemperatureException ex) {
// do nothing
}
}

In a similar manner we test for the exception being thrown when the attempted conversion involves a temperature in Celsius which is lower than the absolute zero.

The InputFilter tests

We want to filter the input that is received by the conversion utility so no garbage reaches this point.

The EditNumber class already filters valid input and generates exceptions otherwise. We can verify this condition by generating some new tests in TemperatureConverterActivityTests. We choose this class because we are sending keys to the entry fields, just as a real user would do:

public void testInputFilter() throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mCelsius.requestFocus();
}
});
final Double n = -1.234d;
sendKeys("MINUS 1 PERIOD 2 PERIOD 3 PERIOD 4");
Object nr = null;
try {
nr = mCelsius.getNumber();
}
catch (NumberFormatException e) {
nr = mCelsius.getText();
}
final String msg = "-1.2.3.4 should be filtered to " + n +
" but is " + nr;
assertEquals(msg, n, nr);
}

This test requests the focus to the Celsius field using the pattern we have reviewed before to run parts of a test in the UI thread, and then send some keys. The keys sent are an invalid sequence containing more than one period, which is not accepted for a well formed decimal number. It is expected that when the filter is in place, this sequence will be filtered and only the valid characters reach the field. We use the possibly generated NumberFormatException to detect the error and then we assert that the value returned by mCelsius.getNumber() is what we are expecting after filtering.

To implement this filter, we need to add an InputFilter to EditNumber. Because this should be added to all of the constructors we create an additional method init() which we invoke from them. To achieve our goal we use an instance of DigitsKeyListener accepting digits, signs, and decimal points.

/**
* Initialization.
* Set filter.
*
*/
private void init() {
// DigistKeyListener.getInstance(true, true) returns an
// instance that accepts digits, sign and decimal point
final InputFilter[] filters = new InputFilter[]
{ DigitsKeyListener.getInstance(true, true) };
setFilters(filters);
}

Then from the constructors we should invoke this method:
/**
* @param context
* @param attrs
*/
public EditNumber(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

This init method is factored and invoked from different constructors.

Running the tests again we can verify that all have passed and now everything is green again.

Viewing our final application

This is our final application which satisfies all the requirements.

In the following screenshot we are showing one of these requirements, which is the detection of an attempt to convert a temperature below the absolute zero temperature in Celsius (-1000.00C):

Android Application Testing: Adding Functionality to the UI

The UI respects the guidelines provided; the temperatures can be converted by entering them in the corresponding unit field.

To recap, this was the list of requirements:

  • The application converts temperatures from Celsius to Fahrenheit and vice-versa
  • The user interface presents two fields to enter the temperatures, one for Celsius other for Fahrenheit
  • When one temperature is entered in one field the other one is automatically updated with the conversion
  • If there are errors, they should be displayed to the user, possibly using the same fields
  • Some space in the user interface should be reserved for the on screen keyboard to ease the application operation when several conversions are entered
  • Entry fields should start empty
  • Values entered are decimal values with two digits after the point
  • Digits are right aligned

But what is perhaps more important is that we can assure that the application not only satisfies the requirements but also has no evident problems or bugs because we took every step by analyzing the test results and fixing the problems at their first appearance. This will ensure that the same bug, once discovered, will not resurface again.

Summary

In this article we implemented tests followed by the code that satisfies the tests.


Further resources related to this subject:


Android Application Testing Guide Build intensively tested and bug free Android applications
Published: June 2011
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Diego Torres Milano

Diego Torres Milano has been involved with the Android platform since its inception, when he started exploring and researching the platform’s possibilities, mainly in the areas of User Interfaces, Unit and Acceptance Tests, and Test Driven Development. This is reflected from a number of articles published mainly on his personal blog (dtmilano.blogspot.com) and his participation as a lecturer in various conferences and courses such as Mobile Dev Camp 2008 in Amsterdam (Netherlands), and Japan Linux Symposium 2009 (Tokyo), Droidcon 2009 (London), Skillsmatter 2009 (London). He has also authored Android training courses delivered to various companies in Europe.

Diego is the founder and developer of several Open Source projects, mainly CULT Universal Linux Thin Project, Autoglade, Gnome-tla, JGlade, and has been contributing to various Linux distributions such as RedHat, Fedora, and Ubuntu.

Apart from giving presentations in Linux World, LinuxTag, GUADEC ES, University of Buenos Aires, etc, Diego has been developing software, participating in Open Source projects and advising companies worldwide for more than 15 years.

Books From Packt


Flash Development for Android Cookbook
Flash Development for Android Cookbook

Android User Interface Development: Beginner's Guide
Android User Interface Development: Beginner's Guide

Android 3.0 Application Development Cookbook
Android 3.0 Application Development Cookbook

iPhone JavaScript Cookbook
iPhone JavaScript Cookbook

Xcode 4 iPhone Development Beginner's Guide
Xcode 4 iPhone Development Beginner's Guide

Core Data iOS Essentials
Core Data iOS Essentials

Cocos2d for iPhone 0.99 Beginner's Guide
Cocos2d for iPhone 0.99 Beginner's Guide

MeeGo 1.0 Mobile Application Development Cookbook
MeeGo 1.0 Mobile Application Development Cookbook


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
w
8
b
q
d
c
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software