Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android Testing

Android Testing

Build a toolset to help you write rock-solid apps

Prateek Srivastava

February 04, 2014
Tweet

More Decks by Prateek Srivastava

Other Decks in Technology

Transcript

  1. Android Testing

  2. Unit Testing Small scoped, isolated

  3. public void maximizeVolume(AudioManager audioManager) { if (audioManager.getRingerMode() != RINGER_MODE_SILENT) {

    int max = audioManager.getStreamMaxVolume(STREAM_RING); audioManager.setStreamVolume(STREAM_RING, max, 0); } }
  4. JUnit http://junit.org/

  5. public void testNormalRingerIsMaximized() { audioManager.setRingerMode(RINGER_MODE_NORMAL); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), max); } !

    public void testSilentRingerIsNotDisturbed() { audioManager.setRingerMode(RINGER_MODE_SILENT); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), 0); }
  6. java.lang.RuntimeException: Stub!

  7. public void setRingerMode(int mode) { throw new RuntimeException("Stub!"); }

  8. Can’t Test Outside A Device!

  9. Can’t Test Outside A Device!

  10. ^ Can’t Test Outside A Device! Wouldn’t Want To

  11. Tests Only Non-Android API's

  12. Android Testing Framework https://developer.android.com/tools/testing/ testing_android.html

  13. public void testNormalRingerIsMaximized() { audioManager.setRingerMode(RINGER_MODE_NORMAL); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), max); } !

    public void testSilentRingerIsNotDisturbed() { audioManager.setRingerMode(RINGER_MODE_SILENT); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), 0); }
  14. Tests Real Android API’s

  15. Run on Real Devices

  16. Slow!

  17. Fast Keep ‘em

  18. Robolectric http://robolectric.org/

  19. @RunWith(RobolectricTestRunner.class) public void testNormalRingerIsMaximized() { audioManager.setRingerMode(RINGER_MODE_NORMAL); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), max); }

    ! @RunWith(RobolectricTestRunner.class) public void testSilentRingerIsNotDisturbed() { audioManager.setRingerMode(RINGER_MODE_SILENT); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), 0); }
  20. @RunWith(RobolectricTestRunner.class) public void testNormalRingerIsMaximized() { audioManager.setRingerMode(RINGER_MODE_NORMAL); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), max); }

    ! @RunWith(RobolectricTestRunner.class) public void testSilentRingerIsNotDisturbed() { audioManager.setRingerMode(RINGER_MODE_SILENT); maximizeVolume(audioManager); assertEquals(audioManager.getStreamVolume(STREAM_RING), 0); }
  21. Tests Fail Occasionally.

  22. expected: <0> but was: <30>

  23. ?

  24. Concise Keep ‘em

  25. fest-android http://square.github.io/fest-android/

  26. @RunWith(RobolectricTestRunner.class) public void testNormalRingerIsMaximized() { audioManager.setRingerMode(RINGER_MODE_NORMAL); maximizeVolume(audioManager); assertThat(audioManager).hasStreamVolume(STREAM_RING, max); }

    ! @RunWith(RobolectricTestRunner.class) public void testSilentRingerIsNotDisturbed() { audioManager.setRingerMode(RINGER_MODE_SILENT); maximizeVolume(audioManager); assertThat(audioManager).hasStreamVolume(STREAM_RING, 0); }
  27. @RunWith(RobolectricTestRunner.class) public void testNormalRingerIsMaximized() { audioManager.setRingerMode(RINGER_MODE_NORMAL); maximizeVolume(audioManager); assertThat(audioManager).hasStreamVolume(STREAM_RING, max); }

    ! @RunWith(RobolectricTestRunner.class) public void testSilentRingerIsNotDisturbed() { audioManager.setRingerMode(RINGER_MODE_SILENT); maximizeVolume(audioManager); assertThat(audioManager).hasStreamVolume(STREAM_RING, 0); }
  28. Expected ring stream volume <0> but was <30>.

  29. Reliable Keep ‘em

  30. Mockito https://code.google.com/p/mockito/

  31. public void testSilentRingerIsNotDisturbed() { // Prepare mocks and script their

    behavior. AudioManager audioManager = Mockito.mock(AudioManager.class); Mockito.when(audioManager.getRingerMode()) .thenReturn(RINGER_MODE_SILENT); ! // Test the code of interest. maximizeVolume(audioManager); ! // Validate that we saw exactly what we wanted. Mockito.verify(audioManager).getRingerMode(); Mockito.verifyNoMoreInteractions(audioManager); }
  32. public void testNormalRingerIsMaximized() { // Prepare mocks and script their

    behavior. AudioManager audioManager = mock(AudioManager.class); when(audioManager.getRingerMode()) .thenReturn(RINGER_MODE_NORMAL); when(audioManager.getStreamMaxVolume(STREAM_RING)) .thenReturn(100); ! // Test the code of interest. maximizeVolume(audioManager); ! // Validate that we saw exactly what we wanted. verify(audioManager) .setStreamVolume(STREAM_RING, 100, 0); }
  33. Verify Interactions Also Great for Integration Testing!

  34. Integration Testing Larger scoped, verify interactions

  35. Monkey https://developer.android.com/tools/help/ monkey.html

  36. Not MonkeyRunner! https://developer.android.com/tools/help/ monkeyrunner_concepts.html

  37. adb shell monkey -p your.package.name -v 500

  38. Low Cost

  39. Simple

  40. Useless* *well, almost

  41. Instrumentation API https://developer.android.com/reference/android/app/ Instrumentation.html

  42. public void testPasswordTooShort_ShowsError() { // Make sure the initial state

    does not show any errors. assertThat(username).hasNoError(); assertThat(password).hasNoError(); instrumentation.runOnMainSync(new Runnable() { @Override public void run() { // Type a value into the username and password fields. username.setText("prateek"); password.setText(“password”); // Click the "login" button. login.performClick(); } }); instrumentation.waitForIdleSync(); ! // Verify error was shown only for username field. assertThat(username).hasNoError(); assertThat(password).hasError(R.string.password_length); }
  43. Much Boilerplate!

  44. Robotium https://code.google.com/p/robotium/

  45. public void testPasswordTooShort_ShowsError() { // Make sure the initial state

    does not show any errors. assertThat(username).hasNoError(); assertThat(password).hasNoError(); ! // Type a value into the username and password fields. solo.typeText(username, “prateek”); solo.typeText(password, “password”); // Click the "login" button. solo.clickOnView(login); ! // Verify error was shown only for username field. assertThat(username).hasNoError(); assertThat(password).hasError(R.string.password_length); }
  46. Espresso https://code.google.com/p/android-test-kit/wiki/ Espresso

  47. Double Espresso https://github.com/JakeWharton/double-espresso

  48. public void testPasswordTooShort_ShowsError() { // Make sure the initial state

    does not show any errors. assertThat(username).hasNoError(); assertThat(password).hasNoError(); ! // Type a value into the username and password fields. onView(withId(R.id.username)).perform(typeText("prateek")); onView(withId(R.id.password)).perform(typeText("password")); onView(withId(R.id.login)).perform(click()); ! // Verify error was shown only for username field. assertThat(username).hasNoError(); assertThat(password).hasError(R.string.password_length); }
  49. Spoon http://square.github.io/spoon/

  50. public void testPasswordTooShort_ShowsError() { Spoon.screenshot(activity, "initial_state"); assertThat(username).hasNoError(); assertThat(password).hasNoError(); ! onView(withId(R.id.username)).perform(typeText("prateek"));

    onView(withId(R.id.password)).perform(typeText("password")); Spoon.screenshot(activity, "values_entered"); onView(withId(R.id.login)).perform(click()); Spoon.screenshot(activity, "login_clicked"); ! assertThat(username).hasNoError(); assertThat(password).hasError(R.string.password_length); }
  51. public void testPasswordTooShort_ShowsError() { Spoon.screenshot(activity, "initial_state"); assertThat(username).hasNoError(); assertThat(password).hasNoError(); ! onView(withId(R.id.username)).perform(typeText("prateek"));

    onView(withId(R.id.password)).perform(typeText("password")); Spoon.screenshot(activity, "values_entered"); onView(withId(R.id.login)).perform(click()); Spoon.screenshot(activity, "login_clicked"); ! assertThat(username).hasNoError(); assertThat(password).hasError(R.string.password_length); }
  52. Spoon.screenshot(Activity, TAG);

  53. None
  54. None