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

Android Testing Support Library - the Nitty Gritty

Android Testing Support Library - the Nitty Gritty

Talk on the intricacies of the Android Testing Support Library given at CodeMobile conference in Chester

Zan Markan

April 18, 2017
Tweet

More Decks by Zan Markan

Other Decks in Programming

Transcript

  1. Who the f*ck is Zan? • Android developer (has been)

    • Loves testing (still does) • ! => " Dabbling in libraries now @zmarkan 2017
  2. Pusher /ˈpʊʃ ər/ - @pusher Check us out if you

    want: • a super cool t-shirt ! <- just tweet to @pusher! • a chat API " • tools to make your life easier # • a job $ @zmarkan 2017
  3. Testing 101 • on JVM vs on device • unit

    / integration / functional / end to end • Robolectric, Calabash, Instrumentation, Robotium, Appium @zmarkan 2017
  4. "Support" • Android framework vs Support libraries • Trend to

    unbundle • support-v4, appcompat-v7, recyclerview, ... @zmarkan 2017
  5. More jUnit3 goodness • overriding setUp() and tearDown() • testPrefixedMethods()

    & test_prefixedMethods() • Ignorance Inheritance is bliss @zmarkan 2017
  6. Welcome to the present • jUnit4 syntax • No more

    extending • @Test, @Before, @After, @AfterClass,... • ActivityTestRule, InstrumentationRegistry @zmarkan 2017
  7. What else is in there? • Espresso • More Espresso

    (there's a lot to it) • UIAutomator • Test Runner • Test Rules • ... @zmarkan 2017
  8. But First... The setup (note: AS does that on it's

    own if you create a new project - these instructions will mostly apply for legacy projects) @zmarkan 2017
  9. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Voila! @zmarkan 2017
  10. Error:Conflict with dependency 'com.android.support:support-annotations'. Resolved versions for app (23.2.1) and

    test app (23.1.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details. @zmarkan 2017
  11. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Voila! • Resolve dependencies @zmarkan 2017
  12. Dependency resolutions • App and Test app depend on different

    lib versions • Run ./gradlew :app:dependencies • ! in the compile and androidTestCompile tasks @zmarkan 2017
  13. Resolve with • Force dependency versions in the test APK

    • exclude dependency (everywhere applicable) • use Resolution strategy @zmarkan 2017
  14. Force versions in test APK // Resolve conflicts between main

    and test APK: androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion" androidTestCompile "com.android.support:support-v4:$rootProject.supportLibraryVersion" Source: github.com/googlesamples/android-architecture/blob/todo- mvp/todoapp/app/build.gradle @zmarkan 2017
  15. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Resolve dependencies • Voila! @zmarkan 2017
  16. Espresso - components • View interactions & assertions • Hamcrest

    syntax • No (unnecessary) waits @zmarkan 2017
  17. Espresso++ • Custom matchers • Custom ViewActions • Idling resources

    • Page objects • Intent mocking @zmarkan 2017
  18. Custom matchers • Find a view in the hierarchy •

    Good for custom views & components • Override: • describeTo • matchesSafely @zmarkan 2017
  19. Custom matchers interface MyView{ boolean hasCustomProperty(); } static BoundedMatcher withCustomPropery()

    { return new BoundedMatcher<Object, MyView>(MyView.class){ @Override public void describeTo(Description description) { description.appendText("Custom property is enabled"); } @Override protected boolean matchesSafely(MyView item) { return item.hasCustomProperty(); } }; } @zmarkan 2017
  20. Custom ViewActions • Same story as matchers, just a bit

    more extensive • example allows us to scroll in NestedScrollView • github.com/zmarkan/Android-Espresso-ScrollableScroll @zmarkan 2017
  21. Custom ViewActions - API public class ScrollableUtils { public static

    ViewAction scrollableScrollTo() { return actionWithAssertions(new ScrollableScrollToAction()); } } @zmarkan 2017
  22. Implementation... public class ScrollableScrollToAction implements ViewAction{ private static final String

    TAG = com.zmarkan.nestedscroll.action.ScrollableScrollToAction.class.getSimpleName(); @SuppressWarnings("unchecked") @Override public Matcher<View> getConstraints() { return allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), isDescendantOfA(anyOf( isAssignableFromClassOrInterface(ScrollingView.class)))); } @Override public void perform(UiController uiController, View view) { if (isDisplayingAtLeast(90).matches(view)) { Log.i(TAG, "View is already displayed. Returning."); return; } Rect rect = new Rect(); view.getDrawingRect(rect); if (!view.requestRectangleOnScreen(rect, true /* immediate */)) { Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled."); } uiController.loopMainThreadUntilIdle(); if (!isDisplayingAtLeast(90).matches(view)) { throw new PerformException.Builder() .withActionDescription(this.getDescription()) .withViewDescription(HumanReadables.describe(view)) .withCause(new RuntimeException( "Scrolling to view was attempted, but the view is not displayed")) .build(); } } @Override public String getDescription() { return "scroll to"; } } @zmarkan 2017
  23. Implementation... @Override public void perform(UiController uiController, View view) { if

    (isDisplayingAtLeast(90).matches(view)) { Log.i(TAG, "View is already displayed. Returning."); return; } Rect rect = new Rect(); view.getDrawingRect(rect); if (!view.requestRectangleOnScreen(rect, true /* immediate */)) { Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled."); } uiController.loopMainThreadUntilIdle(); if (!isDisplayingAtLeast(90).matches(view)) { throw new PerformException.Builder() .withActionDescription(this.getDescription()) .withViewDescription(HumanReadables.describe(view)) .withCause(new RuntimeException( "Scrolling to view was attempted, but the view is not displayed")) .build(); } } @zmarkan 2017
  24. Don't panic. • Take something that works • ...like scrollTo()

    in regular Espresso for ScrollView • modify it • profit @zmarkan 2017
  25. Custom IdlingResource • A better way to wait • Use

    when you have background stuff going on • Override: • getName • isIdleNow • registerIdleTransitionCallback @zmarkan 2017
  26. Custom IdlingResource public class CustomIdlingResource implements IdlingResource{ private ResourceCallback resourceCallback;

    private EventBus bus; private boolean loadingCompleted = false; public CustomIdlingResource(EventBus bus){ bus.register(this); } public void onEvent(LoadingCompletedEvent event){ loadingCompleted = true; isIdleNow(); } } @zmarkan 2017
  27. Custom IdlingResource @Override public String getName() { return CustomIdlingResource.class.getName(); }

    @Override public void registerIdleTransitionCallback( ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } @Override public boolean isIdleNow() { boolean idle = loadingCompleted; if (idle && resourceCallback != null) { resourceCallback.onTransitionToIdle(); bus.unregister(this); } return idle; } } @zmarkan 2017
  28. Page Object Pattern • Build your own DSL • Every

    screen is a Page • Page-specific actions and verifications • (as seen in Calabash, etc...) @zmarkan 2017
  29. Page Class public class MainViewTestUtils { public void enterUserName(String text){

    /* espresso goes here */ } public void enterPassword(String text){ /* ... */ } public void pressContinue(){ /* ... */ } public void assertErrorShown(boolean shown){ /* ... */ } } @zmarkan 2017
  30. Page Class in test @Test public void errorShownWhenPasswordIncorrect(){ MainViewTestUtils view

    = new MainViewTestUtils(); view.enterUsername(username); view.enterPassword(incorrectPassword); view.pressContinue(); view.assertErrorShown(true); } @zmarkan 2017
  31. UI Automator • Interact with any installed app • Use

    to create full end-to-end tests • Can coexist with Espresso in the same app • Use uiautomatorviewer command to find items in the hierarchy @zmarkan 2017
  32. Test Runner • Spins up tests... • and runs them

    • Customise to prepare mocks • Easier run/debug via command line @zmarkan 2017
  33. Extending the Runner • Use custom Application class • Provide

    mocked dependencies • Specify this new runner in build.gradle • Kotlin Test Runner @zmarkan 2017
  34. Extending the Runner public class CustomRunner extends AndroidJUnitRunner{ @Override public

    Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return Instrumentation.newApplication(TestApplication.class, context); } } @zmarkan 2017
  35. Running on the Command line • Run module/package/class/method • Run

    Large/Medium/Small test only • Shard to run in parallel • Debug without reinstalling @zmarkan 2017
  36. Simple API • send commands to runner via ADB (adb

    shell [commands]) • am instrument -w -e class your.full.classname#methodName your.test.package.name/your.test.Runner.class • d.android.com/tools/testing/testing_otheride.html @zmarkan 2017
  37. Test Rules • Set starting activity / Service • Replace

    ActivityInstrumentationTestCase2 • (in most cases) • Add / Extend to create more components @zmarkan 2017
  38. Test Rules - examples • MockWebServerRule - sets up MockWebServer

    when required • Source: github.com/artem-zinnatullin/qualitymatters @zmarkan 2017
  39. Firebase test lab • Simple setup • CI support (via

    gcloud) • Support for VMs • firebase.google.com/docs/test-lab • Robo test for automated testing @zmarkan 2017
  40. Espresso Test Recorder • Since AS 2.2 preview 3 •

    Generates test code after clicking on screen • (Not necessarily nice code) • tools.android.com/tech-docs/test-recorder @zmarkan 2017
  41. • google.github.io/android-testing-support-library/contribute • Quality Matters - Artem Zinnatulin • d.android.com/tools/testing-support-library

    • github.com/googlesamples/android-testing • chiuki.github.io/advanced-android-espresso @zmarkan 2017
  42. • Espresso: Brian Legate (Flickr) • Hyperdrive: Youtube • Road

    Runner: BrownZelip (Flickr) • Back to the future: Youtube • Titanic / unit tests passing: Twitter @zmarkan 2017