Testing on Android at KdG

103e1ebcacd620770cf32a36b9aba17e?s=47 AppFoundry
February 02, 2016

Testing on Android at KdG

A presentation by Filip Maelbrancke (AppFoundry) for the Karel de Grote University College (KdG)

103e1ebcacd620770cf32a36b9aba17e?s=128

AppFoundry

February 02, 2016
Tweet

Transcript

  1. Testing on Android

  2. Your host Filip Maelbrancke Consultant @ AppFoundry filip.maelbrancke@appfoundry.be @fmaelbrancke

  3. About us Consulting Staffing In-house projects

  4. AppFoundry appfoundry.be

  5. Testing

  6. Testing

  7. Testing

  8. Confidence Refactor possible Code handover Fix bugs once

  9. Android history

  10. None
  11. None
  12. Instrumentation Process Test package InstrumentationTestRunner Application package Test case classes

    JUnit
  13. // 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
  14. None
  15. None
  16. Testing tools Android Studio / Gradle Android Testing Support Library

    Espresso
  17. AndroidJUnitRunner Espresso UIAutomator

  18. Unit tests Integration / UI tests Other Unit tests

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

  20. Easy to execute Easy to debug Realistic Fluent API Device,

    emulator, … …
  21. Mockable Android Jar

  22. Demo notes app

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

  24. Model View Presenter Model Presenter View

  25. Model View Presenter Model Presenter View Android UI

  26. Dependency injection Dagger

  27. None
  28. Expressiveness 

  29. Hamcrest assertThat(audience, is(payingAttention())) Verification Mocking Assertions

  30. Hamcrest Library of matchers Ŷ Syntactic sugar Ŷ Assert assertThat(someString,

    is(equalTo(“Expected”))); actual value expectation on the value (Matcher)
  31. Hamcrest Assert Hamcrest assertEquals(“Conversion failed”, “Expected”, someString); assertThat(someString, is(equalTo(“Expected”)));

  32. Hamcrest: collections Assert assertEquals(person, bigbangCharacters.getPersons().iterator().next()); Hamcrest assertThat(bigbangCharacters.getPersons(), hasItem(person));

  33. Hamcrest matchers Core anything
 is
 … Logical allOf
 anyOf
 not

    Object equalTo
 instanceOf
 (not)nullValue Custom Beans hasProperty Text equalToIgnoringCase
 startsWith
 … Number greaterThan(OrEqualTo)
 closeTo Collections hasItem, hasItems
 hasItemInArray
  34. Hamcrest Lots of useful matchers Ŷ Custom matchers Ŷ not

    a number public void testSquareRootOfMinusOneIsNotANumber() {
 assertThat(Math.sqrt(-1), is(notANumber()));
 }
  35. Hamcrest custom matcher Hamcrest matcher /**
 * Hamcrest matcher: test

    if a double has the value NaN (not a number)
 */
 public class IsNotANumber extends TypeSafeMatcher<Double> {
 
 @Override
 public boolean matchesSafely(Double number) {
 return number.isNaN();
 }
 
 public void describeTo(Description description) {
 description.appendText("not a number");
 }
 
 @Factory
 public static <T> Matcher<Double> notANumber() {
 return new IsNotANumber();
 }
 }
  36. None
  37. Find, perform, check onView(ViewMatcher) .perform(ViewAction) .check(ViewAssertion);

  38. 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());
  39. Espresso API onView(Matcher<View>) onData(Matcher<Object>) onView(Matcher<View>) onView(Matcher<View>) withId() / withText() is()

    / instanceOf() click() / enterText() / scrollTo() matches(Matcher<View>) isDisplayed / isEnabled / hasFocus withText / … Find stuff Do stuff Check
  40. Espresso cheat sheet https://github.com/googlesamples/android-testing/tree/master/downloads

  41. Espresso: add note

  42. Espresso test anatomy @RunWith(AndroidJUnit4.class) @LargeTest public class NotesScreenTest { @Rule

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

    public ActivityTestRule<NotesActivity> notesActivityTestRule = new ActivityTestRule<>(NotesActivity.class);
  44. 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()));
 }
  45. 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()));
 }
  46. 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()));
 }
  47. None
  48. Screen Object Pattern public class LoginScreen {
 
 private static

    final ViewInteraction USERNAME_EDIT = onView(withId(R.id.login_username));
 private static final ViewInteraction PASSWORD_EDIT = onView(withId(R.id.login_password));
 private static final ViewInteraction LOGIN_BUTTON = onView(withId(R.id.login_button));
 public static final ViewInteraction LOGIN_VALIDATION_USERNAME_INVALID_FORMAT = onView(withId(R.id.login_validation_username));
 public static final ViewInteraction LOGIN_VALIDATION_INVALID_PASSWORD = onView(withId(R.id.login_validation_password));
 
 public void typeUsername(String username) {
 USERNAME_EDIT.perform(clearText(), typeText(username));
 }
 
 public void typePassword(String password) {
 PASSWORD_EDIT.perform(clearText(), typeText(password));
 }
 
 public void clickLoginButton() {
 LOGIN_BUTTON.perform(scrollTo(), click());
 }
 
 public void doLogin(String username, String password) {
 typeUsername(username);
 typePassword(password);
 clickLoginButton();
 }
 }
  49. Espresso: Intents

  50. Espresso: Intents

  51. 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")); }
  52. Intent stubbing (Mockito for Intents) @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))); }
  53. Intent matchers intended(allOf( hasAction(Intent.ACTION_CHOOSER), hasExtra(is(Intent.EXTRA_INTENT), allOf( hasAction(Intent.ACTION_SEND), hasExtra(Intent.EXTRA_TEXT, "Expected url")

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

  55. 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);
  56. Testing network resources

  57. Mocking the Retrofit interface MockRestAdapter sample

  58. Mocking the Retrofit interface Retrofit mock public interface GitHub {


    @GET("/repos/{owner}/{repo}/contributors")
 List<Contributor> contributors(@Path("owner") String owner, @Path("repo") String repo);
 }
  59. Mocking the Retrofit interface Canned data /** A mock implementation

    of the {@link GitHub} API interface. */
 static class MockGitHub implements GitHub {
 private final Map<String, Map<String, List<Contributor>>> ownerRepoContributors;
 
 public MockGitHub() {
 ownerRepoContributors = new LinkedHashMap<String, Map<String, List<Contributor>>>();
 
 // Seed some mock data.
 addContributor("square", "retrofit", "John Doe", 12);
 addContributor("square", "retrofit", "Bob Smith", 2);
 addContributor("square", "retrofit", "Big Bird", 40);
 addContributor("square", "picasso", "Proposition Joe", 39);
 addContributor("square", "picasso", "Keiser Soze", 152);
 }
  60. Mocking the Retrofit interface Mock @Override public List<Contributor> contributors(String owner,

    String repo) {
 Map<String, List<Contributor>> repoContributors = ownerRepoContributors.get(owner);
 if (repoContributors == null) {
 return Collections.emptyList();
 }
 List<Contributor> contributors = repoContributors.get(repo);
 if (contributors == null) {
 return Collections.emptyList();
 }
 return contributors;
 }
  61. Mocking the Retrofit interface Mock // Create a very simple

    REST adapter which points to the GitHub API endpoint.
 RestAdapter restAdapter = new RestAdapter.Builder()
 .setEndpoint(API_URL)
 .build();
 
 // Wrap our REST adapter to allow mock implementations and fake network delay.
 MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
 
 // Instantiate a mock object so we can interact with it later.
 MockGitHub mockGitHub = new MockGitHub();
 // Use the mock REST adapter and our mock object to create the API interface.
 GitHub gitHub = mockRestAdapter.create(GitHub.class, mockGitHub);
 
 // Query for some contributors for a few repositories.
 printContributors(gitHub, "square", "retrofit");
 printContributors(gitHub, "square", "picasso");
  62. Wiremock { "request": { "method": "GET", "url": "/api/mytest" }, "response":

    { "status": 200, "body": "More content\n" } }
  63. Wiremock stubFor(get(urlEqualTo("/some/thing")) .willReturn(aResponse() .withStatus(404) .withBody(“No!!”)));

  64. 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())); }
  65. Unit tests Integration / UI tests Other Performance tests

  66. Performance Testing Harness

  67. Performance

  68. Performance

  69. Custom Test Rules @Rule
 public EnableTestTracing enableTestTracing;
 
 @Rule
 public

    EnablePostTestDumpsys enablePostTestDumpsys;
 
 @Rule
 public EnableLogcatDump enableLogcatDump;
 
 @Rule
 public EnableNetStatsDump enableNetStatsDump;
  70. None
  71. Tips & tricks

  72. Unit tests Integration / UI tests Other Other tests

  73. The Monkey

  74. evil monkey…

  75. Awaitility @Test
 public void test_play_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
  76. Awaitility @Test
 public void test_play_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
  77. Test Driven Development RED FAILING TEST GREEN FIXED TEST BLUE

    REFACTOR
  78. Automatic On every commit / scheduled Unit tests UI integration

    tests with Espresso
  79. Physical devices

  80. 30 phones - tablets

  81. Test devices

  82. Remote

  83. None
  84. None
  85. Creating software = complex Continuous integration Ensure quality Automate high-quality,

    robust and reliable apps tedious / error-prone activities
  86. Reduce risk Continuous integration Reduce overhead Quality Assurance

  87. Automate all the things

  88. Jenkins

  89. Continuous integration 1 2 3 4 CODE & COMMIT BUILD

    & CHECK CI PICKUP REPORT RESULTS
  90. Metrics

  91. Dashboard

  92. Dashing

  93. Build pipeline Checkout / compile Unit tests Test coverage Code

    analysis Create deployable artifact Deploy for automatic QA test Trigger automated QA stage
  94. Auto publish Delivery Promote app to production Automate Google Play

    alpha / beta iTunes Connect without additional tools
  95. Continuous Delivery

  96. Continuous delivery / deliverable Continuous Delivery Deliverable Push on demand

    Confidence of being deployable
  97. Feature-based deployment A successful Git branching model http://nvie.com/posts/a-successful-git-branching-model/

  98. None
  99. None
  100. Test app distribution

  101. Test app distribution

  102. Internal QA Testing Internal alpha testing program Beta testing /

    staged rollout of being deployable
  103. Resources

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

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

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

  107. Resources Wiremock http://wiremock.org/ OKHTTP MockWebServer https://github.com/square/okhttp/tree/master/mockwebserver UI/Application Exerciser Monkey https://developer.android.com/tools/help/monkey.html

    Awaitility https://github.com/jayway/awaitility
  108. 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)
  109. Questions? Filip Maelbrancke Consultant @ AppFoundry filip.maelbrancke@appfoundry.be @fmaelbrancke

  110. None
  111. Thank you!