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

Testing on Android at KdG

AppFoundry
February 02, 2016

Testing on Android at KdG

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

AppFoundry

February 02, 2016
Tweet

More Decks by AppFoundry

Other Decks in Programming

Transcript

  1. // 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
  2. Hamcrest Library of matchers Ŷ Syntactic sugar Ŷ Assert assertThat(someString,

    is(equalTo(“Expected”))); actual value expectation on the value (Matcher)
  3. 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
  4. Hamcrest Lots of useful matchers Ŷ Custom matchers Ŷ not

    a number public void testSquareRootOfMinusOneIsNotANumber() {
 assertThat(Math.sqrt(-1), is(notANumber()));
 }
  5. 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();
 }
 }
  6. 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
  7. Espresso test anatomy @RunWith(AndroidJUnit4.class) @LargeTest public class NotesScreenTest { @Rule

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

    public ActivityTestRule<NotesActivity> notesActivityTestRule = new ActivityTestRule<>(NotesActivity.class);
  9. 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()));
 }
  10. 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()));
 }
  11. 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()));
 }
  12. 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();
 }
 }
  13. 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")); }
  14. 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))); }
  15. 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);
  16. 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);
 }
  17. 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);
 }
  18. 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;
 }
  19. 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");
  20. 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())); }
  21. Custom Test Rules @Rule
 public EnableTestTracing enableTestTracing;
 
 @Rule
 public

    EnablePostTestDumpsys enablePostTestDumpsys;
 
 @Rule
 public EnableLogcatDump enableLogcatDump;
 
 @Rule
 public EnableNetStatsDump enableNetStatsDump;
  22. 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
  23. 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
  24. Creating software = complex Continuous integration Ensure quality Automate high-quality,

    robust and reliable apps tedious / error-prone activities
  25. Continuous integration 1 2 3 4 CODE & COMMIT BUILD

    & CHECK CI PICKUP REPORT RESULTS
  26. Build pipeline Checkout / compile Unit tests Test coverage Code

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

    alpha / beta iTunes Connect without additional tools
  28. 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)