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

    View Slide

  2. Unit Testing
    Small scoped, isolated

    View Slide

  3. public void maximizeVolume(AudioManager audioManager) {
    if (audioManager.getRingerMode() != RINGER_MODE_SILENT) {
    int max = audioManager.getStreamMaxVolume(STREAM_RING);
    audioManager.setStreamVolume(STREAM_RING, max, 0);
    }
    }

    View Slide

  4. JUnit
    http://junit.org/

    View Slide

  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);
    }

    View Slide

  6. java.lang.RuntimeException:
    Stub!

    View Slide

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

    View Slide

  8. Can’t Test Outside A Device!

    View Slide

  9. Can’t Test Outside A Device!

    View Slide

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

    View Slide

  11. Tests Only Non-Android API's

    View Slide

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

    View Slide

  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);
    }

    View Slide

  14. Tests Real Android API’s

    View Slide

  15. Run on Real Devices

    View Slide

  16. Slow!

    View Slide

  17. Fast
    Keep ‘em

    View Slide

  18. Robolectric
    http://robolectric.org/

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  21. Tests Fail
    Occasionally.

    View Slide

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

    View Slide

  23. ?

    View Slide

  24. Concise
    Keep ‘em

    View Slide

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

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  28. Expected ring stream volume <0> but was <30>.

    View Slide

  29. Reliable
    Keep ‘em

    View Slide

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

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  33. Verify Interactions
    Also Great for Integration Testing!

    View Slide

  34. Integration
    Testing
    Larger scoped, verify interactions

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. Low Cost

    View Slide

  39. Simple

    View Slide

  40. Useless*
    *well, almost

    View Slide

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

    View Slide

  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);
    }

    View Slide

  43. Much
    Boilerplate!

    View Slide

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

    View Slide

  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);
    }

    View Slide

  46. Espresso
    https://code.google.com/p/android-test-kit/wiki/
    Espresso

    View Slide

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

    View Slide

  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);
    }

    View Slide

  49. Spoon
    http://square.github.io/spoon/

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  52. Spoon.screenshot(Activity, TAG);

    View Slide

  53. View Slide

  54. View Slide