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

117c1e7c12a469a052f1361b6599af31?s=128

Zan Markan

April 18, 2017
Tweet

Transcript

  1. Android Testing Support Library The Nitty Gritty @zmarkan 2017

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

    • Loves testing (still does) • ! => " Dabbling in libraries now @zmarkan 2017
  3. 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
  4. Testing + Support Lib = ATSL @zmarkan 2017

  5. Testing 101 • on JVM vs on device • unit

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

    unbundle • support-v4, appcompat-v7, recyclerview, ... @zmarkan 2017
  7. "The support library is basically a mountain of hacks" —

    Chris Banes, Google @zmarkan 2017
  8. Android Testing Support Library @zmarkan 2017

  9. Good Old Times... • jUnit3 syntax • Remember ActivityInstrumentationTestCase2<MainActivit>? @zmarkan

    2017
  10. More jUnit3 goodness • overriding setUp() and tearDown() • testPrefixedMethods()

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

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

    (there's a lot to it) • UIAutomator • Test Runner • Test Rules • ... @zmarkan 2017
  13. What else is in there? @zmarkan 2017

  14. 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
  15. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Voila! @zmarkan 2017
  16. 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
  17. Gradle • Set your test runner to be AndroidJUnitRunner •

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

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

    • exclude dependency (everywhere applicable) • use Resolution strategy @zmarkan 2017
  20. 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
  21. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Resolve dependencies • Voila! @zmarkan 2017
  22. @zmarkan 2017

  23. Espresso - components • View interactions & assertions • Hamcrest

    syntax • No (unnecessary) waits @zmarkan 2017
  24. Espresso - API Cheat sheet: google.github.io/android-testing-support-library/docs/espresso/ cheatsheet @zmarkan 2017

  25. Poking the screen onView(withId(R.id.button)).perform(click()); @zmarkan 2017

  26. Poking the screen onView(withId(R.id.button)).perform(click()); • allOf, anyOf, ... • withParent,

    withText... • isDisplayed, isDialog... @zmarkan 2017
  27. Espresso - contrib • RecyclerView • Drawers • Pickers •

    Accessibility @zmarkan 2017
  28. Espresso++ • Custom matchers • Custom ViewActions • Idling resources

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

    Good for custom views & components • Override: • describeTo • matchesSafely @zmarkan 2017
  30. 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
  31. 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
  32. Custom ViewActions - API public class ScrollableUtils { public static

    ViewAction scrollableScrollTo() { return actionWithAssertions(new ScrollableScrollToAction()); } } @zmarkan 2017
  33. 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
  34. Implementation... @Override public Matcher<View> getConstraints() { return allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), isDescendantOfA(anyOf( isAssignableFromClassOrInterface(ScrollingView.class))));

    } @zmarkan 2017
  35. 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
  36. Don't panic. • Take something that works • ...like scrollTo()

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

    when you have background stuff going on • Override: • getName • isIdleNow • registerIdleTransitionCallback @zmarkan 2017
  38. 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
  39. 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
  40. Page Object Pattern • Build your own DSL • Every

    screen is a Page • Page-specific actions and verifications • (as seen in Calabash, etc...) @zmarkan 2017
  41. 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
  42. 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
  43. To boldly go... @zmarkan 2017

  44. 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
  45. UI Automator viewer @zmarkan 2017

  46. UI Automator syntax UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation(); device.pressHome(); device.findObject(new UiSelector().descriptionContains("Google

    search")).clickAndWaitForNewWindow(); @zmarkan 2017
  47. Test Runner @zmarkan 2017

  48. Test Runner • Spins up tests... • and runs them

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

    mocked dependencies • Specify this new runner in build.gradle • Kotlin Test Runner @zmarkan 2017
  50. 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
  51. 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
  52. 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
  53. Test Rules @zmarkan 2017

  54. Test Rules • Set starting activity / Service • Replace

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

    when required • Source: github.com/artem-zinnatullin/qualitymatters @zmarkan 2017
  56. @zmarkan 2017

  57. 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
  58. 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
  59. Above & Beyond? • Espresso Web for WebViews • JankTestHelper

    • Stuff is being added. @zmarkan 2017
  60. Best of all? @zmarkan 2017

  61. It's all open source! @zmarkan 2017

  62. fin • ! www.spacecowboyrocketcompany.com • " @zmarkan • # zan@markan.me

    / zan@pusher.com • $ androidchat.co (zmarkan) • %& @zmarkan @zmarkan 2017
  63. @zmarkan 2017

  64. • 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
  65. • Espresso: Brian Legate (Flickr) • Hyperdrive: Youtube • Road

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