testing on android
Post on 17-Oct-2014
45.773 views
DESCRIPTION
Droidcon London 2009TRANSCRIPT
copyright © 2009 cod technologies ltd www.codtech.com
droidcon 2009testing on android
diego torres [email protected]
london, december 2009
copyright © 2009 cod technologies ltd www.codtech.com
“Never test the depth of the water with both feet.”
Anonymous
copyright © 2009 cod technologies ltd www.codtech.com
agenda
● test driven development
● behavior driven development
● building blocks
● your first tdd android application
● writing tests
copyright © 2009 cod technologies ltd www.codtech.com
resources
● http://android.codtech.com/droidcon2009
copyright © 2009 cod technologies ltd www.codtech.com
after this section you will
● understand test driven development
● be introduced to behavior driven development
● recognize test building blocks
after this section you will...
copyright © 2009 cod technologies ltd www.codtech.com
what is test driven development ?
TDD is the strategy of starting the development
process by the test cases and then provide the software that
satisfies these tests
copyright © 2009 cod technologies ltd www.codtech.com
test driven development
uml activity diagram
copyright © 2009 cod technologies ltd www.codtech.com
kinds of automated tests● unit tests
– written by programmers
– for programmers
– in a programming language
● functional or acceptance tests
– written by business people and QA
– for business people
– in a high level language
check FitNesse www.fitnesse.org
copyright © 2009 cod technologies ltd www.codtech.com
behavior driven development● evolution in the thinking behind TDD● common vocabulary between business and
technology● framework of activity based on three principles
– business and technology should refer to the same system in the same way
– any system should have an identified, verifiable value to the business
– upfront analysis, design and planning all have a diminishing return http://behaviourdriven.org
http://jbehave.org
copyright © 2009 cod technologies ltd www.codtech.com
scenario● text scenario
● defining steps
Given I am not logged inWhen I log in as Liz with a password JBehaverThen I should see a message, "Welcome, Liz!"
@Given("I am not logged in") public void logOut() { currentPage.click("logout"); }
copyright © 2009 cod technologies ltd www.codtech.com
testing on android● android platform integrates a
testing framework● it's based on Junit 3● supports
– unit tests– functional tests– activity tests– mock objects– utilities to simplify test
creationPortions of this page are reproduced from work created and shared by
Google and used according to terms described in the Creative Commons 2.5 Attribution License.
copyright © 2009 cod technologies ltd www.codtech.com
is this all the documentation ?
a framework for writing android test cases and
suites. (?)
copyright © 2009 cod technologies ltd www.codtech.com
ActivityInstrumentationTestCase2● functional testing of single Activity● Activity created used system infrastructure● Activity can be manipulated● Tests can be annotated with @UiThreadTest● Intents can be injected with setActivityIntent()
● sendKeys() can be used to simulate user interaction
copyright © 2009 cod technologies ltd www.codtech.com
uml class diagram
copyright © 2009 cod technologies ltd www.codtech.com
ApplicationTestCase● test Application in controlled environment● basic lyfecycle support
– onCreate() called after createApplication()– tearDown() calls onDestroy()
● mock Context can be injected● Application can be terminated with
terminateApplication()
copyright © 2009 cod technologies ltd www.codtech.com
ActivityUnitTestCase● isolated testing of a single Activity● Activity created with minimal connection to the
system● mocked dependencies can be injected● Activity will not participate in the normal
interactions with the system● some methods should be avoided and will throw
exceptions if called
copyright © 2009 cod technologies ltd www.codtech.com
ProviderTestCase2<T>● isolated testing of a ContentProvider● uses a MockContentResolver to
– access the provider– restricts access to filesystem (db & files)– injects IsolatedContext
● environment managed by setUp() and tearDown()
copyright © 2009 cod technologies ltd www.codtech.com
ServiceTestCase<T>● framework to test Services● basic support for lifecycle
– onCreate() called after startService(Intent) or bindService(Intent)
– depending on how was started tearDown() calls appropriate method
● mock objects can be injected by– setApplication()– setContext()
copyright © 2009 cod technologies ltd www.codtech.com
AndroidTestCase● base class that can be extended to support
different requirements● use this if you need
– isolate components– test custom Views– test permissions– access Resources
copyright © 2009 cod technologies ltd www.codtech.com
ViewAsserts● useful assertions about Views
– Views are aligned in several ways– ViewGroups contain Views– View is on screen– View has specific screen coordinates
copyright © 2009 cod technologies ltd www.codtech.com
TouchUtils● use these methods to generate touch events● clickView() to simulate touching● drag...() to touch and drag● longClick() for touching and holding● scroll...() to simulate different scrolling● tapView() to touch and release quickly
copyright © 2009 cod technologies ltd www.codtech.com
android.test.mockall classes has nonfunctional methods● MockApplication● MockContentResolver● MockContext● MockDialogInterface● MockPackageManager● MockResources
copyright © 2009 cod technologies ltd www.codtech.com
“You know you've achieved perfection in design, not when you have nothing more to add, but when you have nothing more to take away.”
Antoine de SaintExupery
copyright © 2009 cod technologies ltd www.codtech.com
after this section you will
● be able to create a project and its corresponding tests project
● apply test driven development techniques
● get acquainted with android tests
after this section you will...
copyright © 2009 cod technologies ltd www.codtech.com
design
we leave space for keyboard
if there's and error should be
displayed here (O)
converts temperatures
from celsius to
fahrenheit and
vice-versawhen th
e user
enters a
temperature in
one
field the other
is
updated
automatically
copyright © 2009 cod technologies ltd www.codtech.com
create project
Choose the build target
copyright © 2009 cod technologies ltd www.codtech.com
create test project
always create a test project
other values are automatically
selected
copyright © 2009 cod technologies ltd www.codtech.com
create test case
to create the test for the Activity we use ActivityInstrumentationTestCase2<T>
the Activity as the class under test
create method stubs
copyright © 2009 cod technologies ltd www.codtech.com
check generated test case/** * Copyright © 2009 COD Technologies Ltd. All rights reserved. */package com.codtech.android.training.tctdd1.test;
import android.test.ActivityInstrumentationTestCase2;
import com.codtech.android.training.tctdd1.TemperatureConverterActivity;
public class TemperatureConverterActivityTests extendsActivityInstrumentationTestCase2<TemperatureConverterActivity> {
/** * @param name */public TemperatureConverterActivityTests(String name) {
super("com.codtech.android.training.tctdd1",TemperatureConverterActivity.class);
setName(name);}
// setUp() and tearDown() not showed}
adjust stub constructor to pass required arguments
to super
copyright © 2009 cod technologies ltd www.codtech.com
setUp
/* (non-Javadoc) * @see android.test.ActivityInstrumentationTestCase2#setUp() */protected void setUp() throws Exception {
super.setUp();
final TemperatureConverterActivity activity = getActivity();
celsius = (EditText)activity.findViewById(com.codtech.android.training.tctdd1.R.id.celsius);
fahrenheit = (EditText)activity.findViewById(com.codtech.android.training.tctdd1.R.id.fahrenheit);
}
these constants are not yet created, we
need to add the Views and Ids to the
layout
create fields when necessary
copyright © 2009 cod technologies ltd www.codtech.com
layout
Ids should satisfy
previous requirements
copyright © 2009 cod technologies ltd www.codtech.com
add some UI tests@SmallTestpublic void testSimpleCreate() {
assertNotNull(getActivity());
assertNotNull(celsius);assertNotNull(fahrenheit);
ViewAsserts.assertOnScreen(fahrenheit.getRootView(), celsius);ViewAsserts.assertOnScreen(celsius.getRootView(), fahrenheit);
}
@SmallTestpublic void testAlignment() {
ViewAsserts.assertRightAligned(celsius, fahrenheit);ViewAsserts.assertLeftAligned(celsius, fahrenheit);
}
@SmallTestpublic void testFiledsStartEmpty() {
assertTrue("Celsius field starts not empty", "".equals(celsius.getText().toString()));
assertTrue("Fahrenheit field starts not empty", "".equals(fahrenheit.getText().toString()));
}
copyright © 2009 cod technologies ltd www.codtech.com
now some functional tests
@UiThreadTestpublic void testFahrenheitToCelsiusConversion() {
celsius.clear(); fahrenheit.clear();
final double f = 32.5;fahrenheit.requestFocus();fahrenheit.setNumber(f);celsius.requestFocus();final double c = TemperatureConverter.fahrenheitToCelsius(f);final double cr = celsius.getNumber();final double delta = Math.abs(c - cr);final String msg = "" + f + "F should be " + c + "C but was " +
cr + " (delta " + delta + ")";assertTrue(msg, delta < 0.005);
}
we don't have a class implementing
setNumber(), getNumber() and
clear() so we will be creating it soon
we don't have a TemperatureConverter
either
we run the test in the UI thread
copyright © 2009 cod technologies ltd www.codtech.com
and more tests
@SmallTestpublic void testCelsiusToFahrenheitConversion() {
final double c = -105.35;TouchUtils.tapView(this, celsius);sendKeys("MINUS 1 0 5 PERIOD 3 5");final double cr = celsius.getNumber();assertEquals("Send keys should have set " + c + " but set " +
cr, c, cr);final double f = TemperatureConverter.celsiusToFahrenheit(c);final double fr = fahrenheit.getNumber();final double delta = Math.abs(f - fr);final String msg = "" + c + "C should be " + f + "F but was " +
fr + " (delta " + delta + ")";assertTrue(msg, delta < 0.005);
}
create method stub in TemperatureConverter
don't run this test in UI thread
copyright © 2009 cod technologies ltd www.codtech.com
create EditNumber class
extend EditText
create a new
package for views
once we create EditNumber we should refactor our main layout
and fields
copyright © 2009 cod technologies ltd www.codtech.com
create TemperatureConverter class
copyright © 2009 cod technologies ltd www.codtech.com
create a TestCase
test TemperatureConverter
create stubs
copyright © 2009 cod technologies ltd www.codtech.com
select methods to test
copyright © 2009 cod technologies ltd www.codtech.com
complete actual tests
public void testFahrenheitToCelsius() {for (double c: conversionTableDouble.keySet()) {
final double f = conversionTableDouble.get(c); final double cr = TemperatureConverter.fahrenheitToCelsius(f); final double delta = Math.abs(c - cr); final String msg = "" + f + "F should be " + c + "C but is " +
cr + " (delta " + delta + ")"; assertTrue(msg, delta < 0.0001);
}}
public void testCelsiusToFahrenheit() {for (double c: conversionTableDouble.keySet()) {
final double f = conversionTableDouble.get(c); final double fr = TemperatureConverter.celsiusToFahrenheit(c); final double delta = Math.abs(f - fr); final String msg = "" + c + "C should be " + f + "F but is " +
fr + " (delta " + delta + ")" ; assertTrue(msg, delta < 0.0001); }}
copyright © 2009 cod technologies ltd www.codtech.com
test exceptionspublic void testAbsoluteZeroCelsius() {
Exception e = null;
try {TemperatureConverter.celsiusToFahrenheit(-274);
}catch (RuntimeException re) {
}
assertNotNull("Absolute zero C not detected", e);}
public void testAbsoluteZeroFahrenheit() {Exception e = null;
try {TemperatureConverter.fahrenheitToCelsius(-460);
}catch (RuntimeException re) {
e = re;}
assertNotNull("Absolute zero F not detected", e);}
copyright © 2009 cod technologies ltd www.codtech.com
conversion table
private static final HashMap<Double, Double> conversionTableDouble =new HashMap<Double, Double>();
static {// initialize (c, f) pairsconversionTableDouble.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);
}
● simple conversion table to run several tests
copyright © 2009 cod technologies ltd www.codtech.com
run tests
conversion tests fail because we haven't implemented it yet
copyright © 2009 cod technologies ltd www.codtech.com
TemperatureConverterpackage com.codtech.android.training.tctdd1;
public class TemperatureConverter {
public static final double ABSOLUTE_ZERO_C = -273.0d;public static final double ABSOLUTE_ZERO_F = -459.4d;
private static final String ERROR_MESSAGE_BELOW_ZERO_FMT ="Invalid temperature: %.2f%c below absolute zero";
public static double celsiusToFahrenheit(double c) {if (c < ABSOLUTE_ZERO_C) {
throw new RuntimeException(String.format(ERROR_MESSAGE_BELOW_ZERO_FMT, c, 'C'));
}return (c * 1.8d + 32);
}
public static double fahrenheitToCelsius(double f) {if (f < ABSOLUTE_ZERO_F) {
throw new RuntimeException(String.format(ERROR_MESSAGE_BELOW_ZERO_FMT, f, 'F'));
}return ((f - 32) / 1.8d);
}}
copyright © 2009 cod technologies ltd www.codtech.com
run tests
tests fail now because we are doing the conversion right but we are invoking
empty methods in EditNumber that don't
update the ui
conversion tests succeded
copyright © 2009 cod technologies ltd www.codtech.com
implement EditNumber methodspackage com.codtech.android.training.tctdd1.view;
import android.content.Context;import android.util.AttributeSet;import android.widget.EditText;
public class EditNumber extends EditText {
public EditNumber(Context context) {super(context);
}
public EditNumber(Context context, AttributeSet attrs) {super(context, attrs);
}
public void clear() {setText("");
}
public void setNumber(double f) {setText(Double.toString(f));
}
public double getNumber() {return Double.valueOf(getText().toString());
}}
convenience methods delegating to EditText
copyright © 2009 cod technologies ltd www.codtech.com
run tests
a NumberFormatException because we are not changing
the other field when some new temperature is entered and thus the field is empty
copyright © 2009 cod technologies ltd www.codtech.com
onCreate/** Called when the activity is first created. */
@SuppressWarnings("unchecked")@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); celsius = (EditNumber)findViewById(R.id.celsius); fahrenheit = (EditNumber)findViewById(R.id.fahrenheit);
try { Class[] args = new Class[] { double.class }; celsius.addTextChangedListener(
new TemperatureChangedWatcher(fahrenheit, TemperatureConverter.class.getMethod(
"celsiusToFahrenheit", args))); fahrenheit.addTextChangedListener(
new TemperatureChangedWatcher(celsius,TemperatureConverter.class.getMethod(
"fahrenheitToCelsius", args))); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
add change listener to these fields
copyright © 2009 cod technologies ltd www.codtech.com
TemperatureChangedWatcherprivate static class TemperatureChangedWatcher implements TextWatcher {
private EditNumber dest;private Method convert;
public TemperatureChangedWatcher(EditNumber dest, Method convert) {this.dest = dest; this.convert = convert;
}
@Overridepublic void afterTextChanged(Editable s) {}
@Overridepublic void beforeTextChanged(CharSequence s, int start, int count,
int after) {}
@Overridepublic void onTextChanged(CharSequence s, int start, int before,
int count) {
if (!dest.hasWindowFocus() || dest.hasFocus() || s == null ) {return;
}// continued ...
copyright © 2009 cod technologies ltd www.codtech.com
TemperatureChangedWatcher
final String ss = s.toString();if ( "".equals(ss) ) {
dest.clear();return;
}
try {final double result = (Double)
convert.invoke(TemperatureConverter.class,Double.parseDouble(ss));
// display only 2 decimalsdest.setNumber(Math.rint(result*100)/100);
} 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) {dest.setError(e.getCause().getLocalizedMessage());
}}
}
copyright © 2009 cod technologies ltd www.codtech.com
tests succeed
copyright © 2009 cod technologies ltd www.codtech.com
temperature converter
we leave space for keyboard
if there's and error should be
displayed here (O)
converts temperatures
from celsius to
fahrenheit and
vice-versawhen th
e user
enters a
temperature in
one
field the other
is
updated
automatically
copyright © 2009 cod technologies ltd www.codtech.com
instrumentation● Dev Tools Instrumentation TCTDD1 Tests→ →
copyright © 2009 cod technologies ltd www.codtech.com
eclipse● TCTDD1Test Run As Android JUnit Test→ →
copyright © 2009 cod technologies ltd www.codtech.com
command line
diego@bruce:\~$ adb -e shell am instrument -w -e class com.codtech.android.training.tctdd1.test.TemperatureConverterActivityTests com.codtech.android.training.tctdd1.test/android.test.InstrumentationTestRunner
com.codtech.android.training.tctdd1.test.TemperatureConverterActivityTests:.....Test results for InstrumentationTestRunner=.....Time: 5.564
OK (5 tests)
copyright © 2009 cod technologies ltd www.codtech.com
EditNumberTests
AndroidTestCase
copyright © 2009 cod technologies ltd www.codtech.com
select methods to test
copyright © 2009 cod technologies ltd www.codtech.com
constructor and setUp
/** * @param name */public EditNumberTests(String name) {
super();setName(name);
}
/* (non-Javadoc) * @see android.test.AndroidTestCase#setUp() */protected void setUp() throws Exception {
super.setUp();mockContext = new IsolatedContext(new MockContentResolver(),
getContext());editNumber = new EditNumber(mockContext);editNumber.setFocusable(true);
}
creating a mock context
copyright © 2009 cod technologies ltd www.codtech.com
complete actual tests/** * Test method for EditNumber#EditNumber(android.content.Context) */public void testEditNumberContext() {
assertNotNull(new EditNumber(mockContext));}
/** * Test method for EditNumber#EditNumber(Context, AttributeSet) */public void testEditNumberContextAttributeSet() {
// TODO// use a real AttributeSet for this test not nullassertNotNull(new EditNumber(mockContext, null));
}
/** * Test method for EditNumber#clear() */public void testClear() {
editNumber.setText("1234");editNumber.clear();assertEquals("editNumber should have been cleared",
"", editNumber.getText().toString());}
copyright © 2009 cod technologies ltd www.codtech.com
complete actual tests
public void testSetNumber() {final double d = 1.23d;editNumber.setNumber(d);final double dr = editNumber.getNumber();final String msg = "editNumber should be " + d + " but is " + dr;assertEquals(msg, d, dr);
}
public void testGetNumber() {final double d = 0.98d;final String ds = Double.toString(d);editNumber.setText(ds);final double dr = editNumber.getNumber();final String msg = "editNumber should be " + ds + " but is " + dr;assertEquals(msg, d, dr);
}
copyright © 2009 cod technologies ltd www.codtech.com
input testpublic class TemperatureConverterActivityTests extends
ActivityInstrumentationTestCase2<TemperatureConverterActivity> {
// …
@SmallTestpublic void testInputFilter() {
TouchUtils.tapView(this, celsius);final Double n = -1.234d;sendKeys("MINUS 1 PERIOD 2 PERIOD 3 PERIOD 4");Object nr = null;try {
nr = celsius.getNumber();}catch (NumberFormatException e) {
nr = celsius.getText();}
final String msg = "-1.2.3.4 should be filtered to " + n +" but is " + nr;
assertEquals(msg, n, nr);}
}
copyright © 2009 cod technologies ltd www.codtech.com
input filter
public class EditNumber extends EditText {
// …
/** * Initialization. * Set filter. * */private void init() {
// DigistKeyListener.getInstance(true, true) returns an// instance that accepts digits, sign and decimal pointfinal InputFilter[] filters = new InputFilter[]
{ DigitsKeyListener.getInstance(true, true) };
setFilters(filters);}
}
invoke init() from constructors
copyright © 2009 cod technologies ltd www.codtech.com
resources
● http://dtmilano.blogspot.com
● http://easymock.org
● http://code.google.com/p/hamcrest (matchers)
● http://mockito.org/
● http://developer.android.com/guide/developing/tools/monkey.html (ui exerciser)
● http://ricston.com/ricstontrainingforandroid/
● http://android.codtech.com
copyright © 2009 cod technologies ltd www.codtech.com
“If things seem under control, you're not going fast enough.”
Mario Andretti
copyright © 2009 cod technologies ltd www.codtech.com
thank youtesting on android
diego torres [email protected]
london, december 2009