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

Android Testing at Mobel (December 9, 2015)

Android Testing at Mobel (December 9, 2015)

Slides for the 'Testing on mobile' session (Mobel user group : http://www.meetup.com/mo-bel/events/226864160/)

Filip Maelbrancke

December 09, 2015
Tweet

More Decks by Filip Maelbrancke

Other Decks in Programming

Transcript

  1. Android Testing

    View Slide

  2. Testing

    View Slide

  3. Confidence
    Refactor possible
    Code handover
    Fix bugs once

    View Slide

  4. Android history

    View Slide

  5. View Slide

  6. View Slide

  7. Instrumentation
    Process
    Test package InstrumentationTestRunner Application package
    Test case classes JUnit

    View Slide

  8. // Start the main activity of the application under test

    mActivity = getActivity();

    // Get a handle to the Activity object's main UI widget, a Spinner

    mSpinner = (Spinner) mActivity.findViewById(com.android.example.spinner.R.id.Spinner);

    // Set the Spinner to a known position

    mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);

    // Stop the activity - The onDestroy() method should save the state of the Spinner

    mActivity.finish();

    // Re-start the Activity - the onResume() method should restore the state of the Spinner

    mActivity = getActivity();

    // Get the Spinner's current position

    int currentPosition = mActivity.getSpinnerPosition();

    // Assert that the current position is the same as the starting position

    assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
    Test Activity state saving and restoration

    View Slide

  9. View Slide

  10. View Slide

  11. Testing tools
    Android Studio /
    Gradle
    Android Testing
    Support Library
    Espresso

    View Slide

  12. AndroidJUnitRunner
    Espresso
    UIAutomator

    View Slide

  13. Unit tests
    Integration / UI
    tests
    Other
    Unit tests

    View Slide

  14. (UNIT)
    TESTS
    ISOLATED REPEATABLE FAST SELF-DOCUMENTING
    ž
    Test traits

    View Slide

  15. Easy to execute
    Easy to debug
    Realistic
    Fluent API
    Device, emulator, …

    View Slide

  16. Mockable Android Jar

    View Slide

  17. Demo app
    GearMonster
    http://gearmonster.appfoundry.be/

    View Slide

  18. Google Android Testing Codelab
    Best (introductory) Android
    testing example available
    at the moment (imho)
    https://www.code-labs.io/codelabs/android-testing/

    View Slide

  19. Demo notes app

    View Slide

  20. Model View Presenter
    Model
    Presenter
    View

    View Slide

  21. Model View Presenter
    Model
    Presenter
    View
    Android
    UI

    View Slide

  22. View Slide

  23. View Slide

  24. Find, perform, check
    onView(ViewMatcher)
    .perform(ViewAction)
    .check(ViewAssertion);

    View Slide

  25. Espresso
    Espresso
    onView(withId(R.id.some_id)).perform(click());


    onView(withText("Hi there”)).check(matches(isDisplayed()));


    onView(withId(R.id.some_id)).check(matches(withText(containsString(“someString”))));


    onData(is(someObject)).perform(click());

    View Slide

  26. Espresso API
    onView(Matcher)
    onData(Matcher)
    Espresso
    withId()
    withText()
    ViewMatchers
    perform()
    check()
    ViewInteraction / DataInteraction
    click()
    enterText()
    scrollTo()
    ViewActions
    matches(Matcher)
    doesNotExist()
    ViewAssertions
    find view to operate on
    composed of
    © Stephan Linzner (Google)

    View Slide

  27. Espresso cheat sheet
    https://github.com/googlesamples/android-testing/tree/master/downloads

    View Slide

  28. Espresso: add note

    View Slide

  29. Espresso test anatomy
    @RunWith(AndroidJUnit4.class)
    @LargeTest
    public class NotesScreenTest {
    @Rule
    public ActivityTestRule notesActivityTestRule =
    new ActivityTestRule<>(NotesActivity.class);

    View Slide

  30. Espresso test anatomy
    @RunWith(AndroidJUnit4.class)
    @LargeTest
    public class NotesScreenTest {
    @Rule
    public ActivityTestRule notesActivityTestRule =
    new ActivityTestRule<>(NotesActivity.class);

    View Slide

  31. Espresso: exercise UI
    @Test

    public void addNoteToNotesList() throws Exception {


    // Click on the add note button

    onView(withId(R.id.fab_add_notes)).perform(click());


    // Add note title and description

    onView(withId(R.id.add_note_title)).perform(typeText(“More”));

    onView(withId(R.id.add_note_description)).perform(typeText(“Testing”));


    // Save the note

    onView(withId(R.id.fab_add_notes)).perform(click());


    // Verify note is displayed on screen

    onView(withItemText(“Testing”)).check(matches(isDisplayed()));

    }

    View Slide

  32. Espresso: exercise UI
    @Test

    public void addNoteToNotesList() throws Exception {


    // Click on the add note button

    onView(withId(R.id.fab_add_notes)).perform(click());


    // Add note title and description

    onView(withId(R.id.add_note_title)).perform(typeText(“More”));

    onView(withId(R.id.add_note_description)).perform(typeText(“Testing”));


    // Save the note

    onView(withId(R.id.fab_add_notes)).perform(click());


    // Verify note is displayed on screen

    onView(withItemText(“Testing”)).check(matches(isDisplayed()));

    }

    View Slide

  33. Espresso: exercise UI
    @Test

    public void addNoteToNotesList() throws Exception {


    // Click on the add note button

    onView(withId(R.id.fab_add_notes)).perform(click());


    // Add note title and description

    onView(withId(R.id.add_note_title)).perform(typeText(“More”));

    onView(withId(R.id.add_note_description)).perform(typeText(“Testing”));


    // Save the note

    onView(withId(R.id.fab_add_notes)).perform(click());


    // Verify note is displayed on screen

    onView(withItemText(“Testing”)).check(matches(isDisplayed()));

    }

    View Slide

  34. View Slide

  35. Screen Object Pattern
    public class LoginScreen {


    private static final ViewInteraction USERNAME = onView(withId(R.id.username));

    private static final ViewInteraction PASSWORD = onView(withId(R.id.password));

    private static final ViewInteraction LOGIN = onView(withId(R.id.loginbutton));


    public void typeUsername(String username) {

    USERNAME.perform(clearText(), typeText(username));

    }


    public void typePassword(String password) {

    PASSWORD.perform(clearText(), typeText(password));

    }


    public void clickLoginButton() {

    LOGIN.perform(click());

    }


    }

    View Slide

  36. Espresso: Intents

    View Slide

  37. Espresso: Intents

    View Slide

  38. Intent validation
    @Test
    public void validateIntentSentToPackage() {
    // User action that results in an external "phone" activity being launched.
    user.clickOnView(system.getView(R.id.callButton));
    // Using a canned RecordedIntentMatcher to validate that an intent resolving
    // to the "phone" activity has been sent.
    intended(toPackage("com.android.phone"));
    }

    View Slide

  39. Intent stubbing
    @Test
    public void activityResult_IsHandledProperly() {
    // Build a result to return when a particular activity is launched.
    Intent resultData = new Intent();
    String phoneNumber = "012-345-678";
    resultData.putExtra("phone", phoneNumber);
    ActivityResult result = new ActivityResult(Activity.RESULT_OK, resultData);
    // Set up result stubbing when an intent sent to "contacts" is seen.
    intending(toPackage("com.android.contacts")).respondWith(result));
    // User action that results in "contacts" activity being launched.
    // Launching activity expects phoneNumber to be returned and displays it on the screen.
    onView(withId(R.id.pickButton)).perform(click());
    // Assert that data we set up above is shown.
    onView(withId(R.id.phoneNumber).check(matches(withText(phoneNumber)));
    }

    View Slide

  40. Intent matchers
    intended(allOf(
    hasAction(Intent.ACTION_CHOOSER),
    hasExtra(is(Intent.EXTRA_INTENT),
    allOf(
    hasAction(Intent.ACTION_SEND),
    hasExtra(Intent.EXTRA_TEXT, "Expected url") ))));

    View Slide

  41. Espresso cheat sheet
    https://github.com/googlesamples/android-testing/tree/master/downloads

    View Slide

  42. Espresso synchronization
    public class IdleMonitor implements IdlingResource {


    @Override

    public String getName() {

    return IdleMonitor.class.getSimpleName();

    }


    @Override

    public boolean isIdleNow() {

    // return true if resource is idle

    return false;

    }


    @Override

    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {

    // store a reference to the resourceCallback

    // notify resourceCallback when idle

    }

    }
    Espresso.registerIdlingResources(idlingResource);

    View Slide

  43. Testing network resources

    View Slide

  44. Wiremock
    {
    "request": {
    "method": "GET",
    "url": "/api/mytest"
    },
    "response": {
    "status": 200,
    "body": "More content\n"
    }
    }

    View Slide

  45. Wiremock
    stubFor(get(urlEqualTo("/some/thing"))
    .willReturn(aResponse()
    .withStatus(404)
    .withBody(“No!!”)));

    View Slide

  46. OKHTTP MockWebServer
    public void testServerErrorShouldShowEmptyState() {
    MockServiceModule mockServiceModule = new MockServiceModule();
    DaggerHelper.initWithTestModules(mockServiceModule);
    // Setup the MockWebServer
    MockResponse mockResponse = new MockResponse();
    mockResponse.setResponseCode(500);
    mockServiceModule.getMockWebServer().enqueue(mockResponse);
    // mockwebserver + restadapter are setup, now init activity
    getActivity();
    onView(withId(R.id.list_empty_image)).check(
    matches(isDisplayed()));
    }

    View Slide

  47. Unit tests
    Integration / UI
    tests
    Other
    Performance tests

    View Slide

  48. Performance Testing Harness

    View Slide

  49. Performance

    View Slide

  50. Performance

    View Slide

  51. Custom Test Rules
    @Rule

    public EnableTestTracing enableTestTracing;


    @Rule

    public EnablePostTestDumpsys enablePostTestDumpsys;


    @Rule

    public EnableLogcatDump enableLogcatDump;


    @Rule

    public EnableNetStatsDump enableNetStatsDump;

    View Slide

  52. View Slide

  53. Tips & tricks

    View Slide

  54. Android Unmock Gradle Plugin
    unMock {
    // URI to download the android-all.jar from
    downloadFrom 'https://oss.sonatype.org/content/groups/public/org/robolectric/android-all/4.3_r2-robolectric-0/android-
    all-4.3_r2-robolectric-0.jar'
    keep "android.widget.BaseAdapter"
    keep "android.widget.ArrayAdapter"
    keep "android.os.Bundle"
    keepStartingWith "android.text.TextUtils"
    keepStartingWith "android.util."
    keepStartingWith "android.text."
    keepStartingWith "com.android.internal.R"
    keepStartingWith "com.android.internal.util."
    keep "android.net.Uri"
    }

    View Slide

  55. Unit tests
    Integration / UI
    tests
    Other
    Other tests

    View Slide

  56. The Monkey

    View Slide

  57. evil monkey…

    View Slide

  58. AssertJ Android
    for checking assertions to read / write
    http://square.github.io/assertj-android/
    Syntactic sugar
    Ŷ Extension of the AssertJ library
    Ŷ
    Makes tests easier
    Ŷ
    Fluent syntax
    Ŷ

    View Slide

  59. AssertJ Android
    Regular JUnit
    AssertJ Android
    assertEquals(View.GONE, view.getVisibility());
    assertThat(view).isGone();
    expected: <8> but was: <4>
    Expected visibility but was .

    View Slide

  60. AssertJ Android
    Regular JUnit
    assertEquals(View.VISIBLE, layout.getVisibility());
    assertEquals(VERTICAL, layout.getOrientation());
    assertEquals(4, layout.getChildCount());
    assertEquals(SHOW_DIVIDERS_MIDDLE, layout.getShowDividers());
    AssertJ Android
    assertThat(layout).isVisible()
    .isVertical()
    .hasChildCount(4)
    .hasShowDividers(SHOW_DIVIDERS_MIDDLE);

    View Slide

  61. Awaitility
    @Test

    public void test_mediaPlayer_liveStream() {


    MediaControllerCompat mediaController = new MediaControllerCompat(getContext(), audioPlayerService.getMediaSessionToken());

    String uri = "http://stream.be/service/mp3:web/program_ondemand_128.mp3/playlist.m3u8";

    Bundle extras = new Bundle();

    extras.putBoolean(AudioPlayerService.EXTRA_CONTINUE_ON_RESUME, false);

    mediaController.getTransportControls().playFromMediaId(uri, extras);

    await().atMost(10, TimeUnit.SECONDS).until(musicIsActive());


    }
    Test asynchronous system
    Express expectations in easy to read manner

    View Slide

  62. Spoon

    View Slide

  63. Test Driven Development
    RED
    FAILING TEST
    GREEN
    FIXED TEST
    BLUE
    REFACTOR

    View Slide

  64. Automatic
    On every commit / scheduled
    Unit tests
    UI integration tests
    with Espresso

    View Slide

  65. Test devices

    View Slide

  66. Remote

    View Slide

  67. View Slide

  68. View Slide

  69. Creating software = complex
    Continuous
    integration Ensure quality
    Automate
    high-quality, robust and reliable apps
    tedious / error-prone activities

    View Slide

  70. Reduce risk
    Continuous
    integration Reduce overhead
    Quality Assurance

    View Slide

  71. Automate all the things

    View Slide

  72. Continuous integration
    1
    2
    3
    4
    CODE & COMMIT BUILD & CHECK
    CI PICKUP
    REPORT RESULTS

    View Slide

  73. Metrics

    View Slide

  74. Dashboard

    View Slide

  75. Dashing

    View Slide

  76. Panic…

    View Slide

  77. Build pipeline
    Checkout /
    compile
    Unit tests
    Test
    coverage
    Code
    analysis
    Create
    deployable
    artifact
    Deploy for
    automatic
    QA test
    Trigger
    automated
    QA stage

    View Slide

  78. Auto publish
    Delivery
    Promote app to production
    Automate
    Google Play alpha / beta
    iTunes Connect
    without additional tools

    View Slide

  79. Continuous
    Delivery

    View Slide

  80. Continuous delivery / deliverable
    Continuous
    Delivery
    Deliverable Push on demand
    Confidence
    of being deployable

    View Slide

  81. Feature-based deployment
    A successful Git branching model
    http://nvie.com/posts/a-successful-git-branching-model/

    View Slide

  82. View Slide

  83. View Slide

  84. Test app distribution

    View Slide

  85. Test app distribution

    View Slide

  86. Internal QA
    Testing
    Internal alpha testing program
    Beta testing / staged rollout
    of being deployable

    View Slide

  87. Resources

    View Slide

  88. Android Testing Support Library
    https://google.github.io/android-testing-support-library/

    View Slide

  89. Google Codelabs
    https://www.code-labs.io/

    View Slide

  90. Google Testing Samples / Blueprint
    https://github.com/googlesamples/android-testing/ https://github.com/googlesamples/android-testing-templates/

    View Slide

  91. Resources
    AT&T Application Resource Optimizer (mentioned by Friedger Müffke & Jo Somers)
    https://developer.att.com/application-resource-optimizer
    Wiremock
    http://wiremock.org/
    Android Unmock Gradle Plugin
    https://github.com/bjoernQ/unmock-plugin
    AssertJ Android
    https://github.com/square/assertj-android
    Awaitility
    https://github.com/jayway/awaitility
    Spoon
    https://square.github.io/spoon/

    View Slide

  92. Suggested reading
    Test Driven Development: By Example
    Beck, Kent (978-0321146533)
    Continuous Integration: Improving Software Quality and
    Reducing Risk
    Duvall, Paul M. et al. (978-0321336385)
    Working Effectively with Legacy Code
    Feathers, Michael (978-0131177055)

    View Slide

  93. View Slide

  94. Questions?
    Filip Maelbrancke
    Consultant @ AppFoundry
    [email protected]
    @fmaelbrancke

    View Slide

  95. Thank you!

    View Slide