Slide 1

Slide 1 text

Android Testing

Slide 2

Slide 2 text

Testing

Slide 3

Slide 3 text

Confidence Refactor possible Code handover Fix bugs once

Slide 4

Slide 4 text

Android history

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Instrumentation Process Test package InstrumentationTestRunner Application package Test case classes JUnit

Slide 8

Slide 8 text

// 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

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Testing tools Android Studio / Gradle Android Testing Support Library Espresso

Slide 12

Slide 12 text

AndroidJUnitRunner Espresso UIAutomator

Slide 13

Slide 13 text

Unit tests Integration / UI tests Other Unit tests

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Mockable Android Jar

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Demo notes app

Slide 20

Slide 20 text

Model View Presenter Model Presenter View

Slide 21

Slide 21 text

Model View Presenter Model Presenter View Android UI

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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)

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Espresso: add note

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Espresso: Intents

Slide 37

Slide 37 text

Espresso: Intents

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Testing network resources

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Unit tests Integration / UI tests Other Performance tests

Slide 48

Slide 48 text

Performance Testing Harness

Slide 49

Slide 49 text

Performance

Slide 50

Slide 50 text

Performance

Slide 51

Slide 51 text

Custom Test Rules @Rule
 public EnableTestTracing enableTestTracing;
 
 @Rule
 public EnablePostTestDumpsys enablePostTestDumpsys;
 
 @Rule
 public EnableLogcatDump enableLogcatDump;
 
 @Rule
 public EnableNetStatsDump enableNetStatsDump;

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Tips & tricks

Slide 54

Slide 54 text

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" }

Slide 55

Slide 55 text

Unit tests Integration / UI tests Other Other tests

Slide 56

Slide 56 text

The Monkey

Slide 57

Slide 57 text

evil monkey…

Slide 58

Slide 58 text

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 Ŷ

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Spoon

Slide 63

Slide 63 text

Test Driven Development RED FAILING TEST GREEN FIXED TEST BLUE REFACTOR

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Test devices

Slide 66

Slide 66 text

Remote

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Reduce risk Continuous integration Reduce overhead Quality Assurance

Slide 71

Slide 71 text

Automate all the things

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Metrics

Slide 74

Slide 74 text

Dashboard

Slide 75

Slide 75 text

Dashing

Slide 76

Slide 76 text

Panic…

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

Continuous Delivery

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

Test app distribution

Slide 85

Slide 85 text

Test app distribution

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Resources

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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/

Slide 92

Slide 92 text

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)

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

Questions? Filip Maelbrancke Consultant @ AppFoundry filip.maelbrancke@appfoundry.be @fmaelbrancke

Slide 95

Slide 95 text

Thank you!