Advertisement
Advertisement

More Related Content

Advertisement
Advertisement

Testing on Android

  1. droidcon 2009 testing on android london, december 2009 diego torres milano diego@codtech.com copyright © 2009  cod technologies ltd  www.codtech.com
  2. “Never test the depth of  the water with both feet.” ­­ Anonymous copyright © 2009  cod technologies ltd  www.codtech.com
  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
  5. after this section you will after this section you will... ● understand test driven  development ● be introduced to behavior  driven development ● recognize test building  blocks 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
  7. test driven development uml activity diagram 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 – up­front analysis, design and planning all have a  diminishing return http://behaviour­driven.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
  12. is this all the documentation ? a framework for writing  android test cases and  suites. (?) 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
  14. uml class diagram 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
  22. android.test.mock all classes has non­functional methods ● MockApplication ● MockContentResolver ● MockContext ● MockDialogInterface ● MockPackageManager ● MockResources copyright © 2009  cod technologies ltd  www.codtech.com
  23. “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 Saint­Exupery copyright © 2009  cod technologies ltd  www.codtech.com
  24. after this section you will 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 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
  27. create test project always create a  test project other values are  automatically  selected 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
  36. create TemperatureConverter class copyright © 2009  cod technologies ltd  www.codtech.com
  37. create a TestCase create  stubs test  TemperatureConverter copyright © 2009  cod technologies ltd  www.codtech.com
  38. select methods to test 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
  56. select methods to test 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/ricston­training­for­android/ ● http://android.codtech.com copyright © 2009  cod technologies ltd  www.codtech.com
  63. “If things seem under control,  you're not going fast enough.” ­­ Mario Andretti 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
Advertisement