More Related Content
Similar to Testing on Android (20)
Testing on Android
- 3. agenda
● test driven development
● behavior driven development
● building blocks
● your first tdd android application
● writing tests
copyright © 2009 cod technologies ltd www.codtech.com
- 4. resources
● http://android.codtech.com/droidcon2009
copyright © 2009 cod technologies ltd www.codtech.com
- 6. 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
- 8. 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
- 9. 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
- 10. scenario
● text scenario
Given I am not logged in
When I log in as Liz with a password JBehaver
Then I should see a message, "Welcome, Liz!"
● defining steps
@Given("I am not logged in")
public void logOut() {
currentPage.click("logout");
}
copyright © 2009 cod technologies ltd www.codtech.com
- 11. 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 Portions of this page are reproduced from work created and shared by
Google and used according to terms described in the Creative
creation Commons 2.5 Attribution License.
copyright © 2009 cod technologies ltd www.codtech.com
- 13. 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
- 15. 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
- 16. 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
- 17. 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
- 18. 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
- 19. 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
- 20. 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
- 21. 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
- 25. design
converts
temperatures
from celsius e
r
us to
e
n th
whehrenheit a d
an
if
the
fa
rsa err re'
e- e
t
vicen vers one or s a
nd
atur e in dis
pla
sh o
uld
temper ther
is (O
)
ye
d be
e o he
ld th
re
fie ed
updat
lly
m atica
auto
we l
eave
f or s p ac
keyb e
o ar d
copyright © 2009 cod technologies ltd www.codtech.com
- 26. create project
Choose the
build target
copyright © 2009 cod technologies ltd www.codtech.com
- 28. create test case
to create the test for
the Activity we use
ActivityInstrument
ationTestCase2<T>
create method
stubs
the Activity as the
class under test
copyright © 2009 cod technologies ltd www.codtech.com
- 29. check generated test case
/**
* Copyright © 2009 COD Technologies Ltd. All rights reserved.
*/
package com.codtech.android.training.tctdd1.test;
import android.test.ActivityInstrumentationTestCase2;
import
adjust stub
com.codtech.android.training.tctdd1.TemperatureConverterActivity;
constructor to pass
required arguments
public class
to super
TemperatureConverterActivityTests extends
ActivityInstrumentationTestCase2<TemperatureConverterActivity> {
/**
* @param name
*/
public TemperatureConverterActivityTests(String name) {
super("com.codtech.android.training.tctdd1",
TemperatureConverterActivity.class);
setName(name);
}
// setUp() and tearDown() not showed
}
copyright © 2009 cod technologies ltd www.codtech.com
- 30. setUp
/* (non-Javadoc)
* @see android.test.ActivityInstrumentationTestCase2#setUp()
*/
these constants are
protected void setUp() throws Exception { not yet created, we
super.setUp(); need to add the
Views and Ids to the
final TemperatureConverterActivity activity = getActivity(); layout
celsius = (EditText)activity.findViewById(
com.codtech.android.training.tctdd1.R.id.celsius);
fahrenheit = (EditText)activity.findViewById(
com.codtech.android.training.tctdd1.R.id.fahrenheit);
}
create fields
when necessary
copyright © 2009 cod technologies ltd www.codtech.com
- 31. layout
Ids should
satisfy
previous
requirements
copyright © 2009 cod technologies ltd www.codtech.com
- 32. add some UI tests
@SmallTest
public void testSimpleCreate() {
assertNotNull(getActivity());
assertNotNull(celsius);
assertNotNull(fahrenheit);
ViewAsserts.assertOnScreen(fahrenheit.getRootView(), celsius);
ViewAsserts.assertOnScreen(celsius.getRootView(), fahrenheit);
}
@SmallTest
public void testAlignment() {
ViewAsserts.assertRightAligned(celsius, fahrenheit);
ViewAsserts.assertLeftAligned(celsius, fahrenheit);
}
@SmallTest
public 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
- 33. now some functional tests
we run the test in the
UI thread
@UiThreadTest
public void testFahrenheitToCelsiusConversion() { we don't have a
celsius.clear(); class implementing
fahrenheit.clear(); setNumber(),
getNumber() and
final double f = 32.5; clear() so we will be
creating it soon
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
TemperatureConverter
either
copyright © 2009 cod technologies ltd www.codtech.com
- 34. and more tests
don't run this test
in UI thread
@SmallTest
public void testCelsiusToFahrenheitConversion() {
final double c = -105.35;
TouchUtils.tapView(this, celsius);
create method stub in
sendKeys("MINUS 1 0 5 PERIOD 3 5");
TemperatureConverter
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);
}
copyright © 2009 cod technologies ltd www.codtech.com
- 35. create EditNumber class
create a
new
package for
views
extend
EditText
once we create
EditNumber we
should refactor
our main layout
and fields
copyright © 2009 cod technologies ltd www.codtech.com
- 37. create a TestCase
create
stubs
test
TemperatureConverter
copyright © 2009 cod technologies ltd www.codtech.com
- 39. 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
- 40. test exceptions
public 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
- 41. conversion table
● simple conversion table to run several tests
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);
}
copyright © 2009 cod technologies ltd www.codtech.com
- 42. run tests
conversion tests fail
because we haven't
implemented it yet
copyright © 2009 cod technologies ltd www.codtech.com
- 43. TemperatureConverter
package 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
- 44. run tests
tests fail now because we
are doing the conversion
right but we are invoking
conversion tests empty methods in
succeded EditNumber that don't
update the ui
copyright © 2009 cod technologies ltd www.codtech.com
- 45. implement EditNumber methods
package 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);
} convenience methods
delegating to EditText
public void clear() {
setText("");
}
public void setNumber(double f) {
setText(Double.toString(f));
}
public double getNumber() {
return Double.valueOf(getText().toString());
}
}
copyright © 2009 cod technologies ltd www.codtech.com
- 46. 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
- 47. onCreate
/** Called when the activity is first created. */
@SuppressWarnings("unchecked")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
add change listener
celsius = (EditNumber)findViewById(R.id.celsius); to these fields
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();
}
}
copyright © 2009 cod technologies ltd www.codtech.com
- 48. TemperatureChangedWatcher
private static class TemperatureChangedWatcher implements TextWatcher {
private EditNumber dest;
private Method convert;
public TemperatureChangedWatcher(EditNumber dest, Method convert) {
this.dest = dest; this.convert = convert;
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public 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
- 49. 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 decimals
dest.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
- 50. tests succeed
copyright © 2009 cod technologies ltd www.codtech.com
- 51. temperature converter
converts
temperatures
from celsius e
r
us to
e
n th
whehrenheit a d
an
if
the
fa
rsa err re'
e- e
t
vicen vers one or s a
nd
atur e in dis
pla
sh o
uld
temper ther
is (O
)
ye
d be
e o he
ld th
re
fie ed
updat
lly
m atica
auto
we l
eave
f or s p ac
keyb e
o ar d
copyright © 2009 cod technologies ltd www.codtech.com
- 52. instrumentation
● Dev Tools → Instrumentation → TCTDD1 Tests
copyright © 2009 cod technologies ltd www.codtech.com
- 53. eclipse
● TCTDD1Test → Run As → Android JUnit Test
copyright © 2009 cod technologies ltd www.codtech.com
- 54. command line
diego@bruce:~$ adb -e shell am instrument -w -e class
com.codtech.android.training.tctdd1.test.TemperatureCo
nverterActivityTests
com.codtech.android.training.tctdd1.test/android.test.
InstrumentationTestRunner
com.codtech.android.training.tctdd1.test.TemperatureCo
nverterActivityTests:.....
Test results for InstrumentationTestRunner=.....
Time: 5.564
OK (5 tests)
copyright © 2009 cod technologies ltd www.codtech.com
- 55. EditNumberTests
AndroidTestCase
copyright © 2009 cod technologies ltd www.codtech.com
- 57. constructor and setUp
/**
* @param name
*/
public EditNumberTests(String name) {
super();
setName(name);
}
creating a mock
context
/* (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);
}
copyright © 2009 cod technologies ltd www.codtech.com
- 58. 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 null
assertNotNull(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
- 59. 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
- 60. input test
public class TemperatureConverterActivityTests extends
ActivityInstrumentationTestCase2<TemperatureConverterActivity> {
// …
@SmallTest
public 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
- 61. input filter
public class EditNumber extends EditText {
// …
invoke init() from
constructors
/**
* 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);
}
}
copyright © 2009 cod technologies ltd www.codtech.com
- 62. resources
● http://dtmilano.blogspot.com
● http://easymock.org
● http://code.google.com/p/hamcrest (matchers)
● http://mockito.org/
● http://developer.android.com/guide/developing/t
ools/monkey.html (ui exerciser)
● http://ricston.com/ricstontrainingforandroid/
● http://android.codtech.com
copyright © 2009 cod technologies ltd www.codtech.com
- 64. thank you
testing on android
london, december 2009
diego torres milano
diego@codtech.com
copyright © 2009 cod technologies ltd www.codtech.com