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

Android Testing Support Library: the nitty gritty

Zan Markan
November 26, 2016

Android Testing Support Library: the nitty gritty

Talk delivered at Codemotion Milan 2016

Zan Markan

November 26, 2016
Tweet

More Decks by Zan Markan

Other Decks in Programming

Transcript

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

    / integration / functional / end to end • Robolectric, Calabash, Instrumentation, Robotium, Appium © Zan Markan 2016 - @zmarkan
  2. "Support" • Android framework vs Support libraries • Trend to

    unbundle • support-v4, appcompat-v7, recyclerview, ... © Zan Markan 2016 - @zmarkan
  3. "The support library is basically a mountain of hacks" —

    Chris Banes, Google © Zan Markan 2016 - @zmarkan
  4. More jUnit3 goodness • overriding setUp() and tearDown() • testPrefixedMethods()

    & test_prefixedMethods() • Ignorance Inheritance is bliss © Zan Markan 2016 - @zmarkan
  5. Welcome to the present • jUnit4 syntax • No more

    extending • @Test, @Before, @After, @AfterClass,... • ActivityTestRule, InstrumentationRegistry © Zan Markan 2016 - @zmarkan
  6. What else is in there? • Espresso • More Espresso

    (there's a lot to it) • UIAutomator • Test Runner • Test Rules • ... © Zan Markan 2016 - @zmarkan
  7. 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) © Zan Markan 2016 - @zmarkan
  8. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Voila! © Zan Markan 2016 - @zmarkan
  9. 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. © Zan Markan 2016 - @zmarkan
  10. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Voila! • Resolve dependencies © Zan Markan 2016 - @zmarkan
  11. Dependency resolutions • App and Test app depend on different

    lib versions • Run ./gradlew :app:dependencies • ! in the compile and androidTestCompile tasks © Zan Markan 2016 - @zmarkan
  12. Resolve with • Force dependency versions in the test APK

    • exclude dependency (everywhere applicable) • use Resolution strategy © Zan Markan 2016 - @zmarkan
  13. 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 © Zan Markan 2016 - @zmarkan
  14. Gradle • Set your test runner to be AndroidJUnitRunner •

    Add dependencies • Resolve dependencies • Voila! © Zan Markan 2016 - @zmarkan
  15. Espresso - components • View interactions & assertions • Hamcrest

    syntax • No (unnecessary) waits © Zan Markan 2016 - @zmarkan
  16. Poking the screen onView(withId(R.id.button)).perform(click()); • allOf, anyOf, ... • withParent,

    withText... • isDisplayed, isDialog... © Zan Markan 2016 - @zmarkan
  17. Espresso - contrib • RecyclerView • Drawers • Pickers •

    Accessibility © Zan Markan 2016 - @zmarkan
  18. Espresso++ • Custom matchers • Custom ViewActions • Idling resources

    • Page objects • Intent mocking © Zan Markan 2016 - @zmarkan
  19. Custom matchers • Find a view in the hierarchy •

    Good for custom views & components • Override: • describeTo • matchesSafely © Zan Markan 2016 - @zmarkan
  20. 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(); } }; } © Zan Markan 2016 - @zmarkan
  21. Custom ViewActions • Same story as matchers, just a bit

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

    ViewAction scrollableScrollTo() { return actionWithAssertions(new ScrollableScrollToAction()); } } © Zan Markan 2016 - @zmarkan
  23. 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"; } } © Zan Markan 2016 - @zmarkan
  24. 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(); } } © Zan Markan 2016 - @zmarkan
  25. Don't panic. • Take something that works • ...like scrollTo()

    in regular Espresso for ScrollView • modify it • profit © Zan Markan 2016 - @zmarkan
  26. Custom IdlingResource • A better way to wait • Use

    when you have background stuff going on • Override: • getName • isIdleNow • registerIdleTransitionCallback © Zan Markan 2016 - @zmarkan
  27. 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(); } } © Zan Markan 2016 - @zmarkan
  28. 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; } } © Zan Markan 2016 - @zmarkan
  29. Page Object Pattern • Build your own DSL • Every

    screen is a Page • Page-specific actions and verifications • (as seen in Calabash, etc...) © Zan Markan 2016 - @zmarkan
  30. 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){ /* ... */ } } © Zan Markan 2016 - @zmarkan
  31. Page Class in test @Test public void errorShownWhenPasswordIncorrect(){ MainViewTestUtils view

    = new MainViewTestUtils(); view.enterUsername(username); view.enterPassword(incorrectPassword); view.pressContinue(); view.assertErrorShown(true); } © Zan Markan 2016 - @zmarkan
  32. 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 © Zan Markan 2016 - @zmarkan
  33. Test Runner • Spins up tests... • and runs them

    • Customise to prepare mocks • Easier run/debug via command line © Zan Markan 2016 - @zmarkan
  34. Extending the Runner • Use custom Application class • Provide

    mocked dependencies • Specify this new runner in build.gradle • Kotlin Test Runner © Zan Markan 2016 - @zmarkan
  35. 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); } } © Zan Markan 2016 - @zmarkan
  36. Running on the Command line • Run module/package/class/method • Run

    Large/Medium/Small test only • Shard to run in parallel • Debug without reinstalling © Zan Markan 2016 - @zmarkan
  37. 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 © Zan Markan 2016 - @zmarkan
  38. Test Rules • Set starting activity / Service • Replace

    ActivityInstrumentationTestCase2 • (in most cases) • Add / Extend to create more components © Zan Markan 2016 - @zmarkan
  39. Test Rules - examples • MockWebServerRule - sets up MockWebServer

    when required • Source: github.com/artem-zinnatullin/ qualitymatters © Zan Markan 2016 - @zmarkan
  40. Firebase test lab • Simple setup • CI support (via

    gcloud) • Support for VMs • firebase.google.com/docs/test-lab • Robo test for automated testing © Zan Markan 2016 - @zmarkan
  41. 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 © Zan Markan 2016 - @zmarkan
  42. Above & Beyond? • Espresso Web for WebViews • JankTestHelper

    • Stuff is being added. © Zan Markan 2016 - @zmarkan
  43. Pusher /ˈpʊʃ ər/ noun 1. Platform of APIs for building

    highly connected apps 2. Hiring in London ! © Zan Markan 2016 - @zmarkan
  44. fin • ! www.spacecowboyrocketcompany.com • " @zmarkan • # zan

    at markan dot me • $ androidchat.co (zmarkan) • %& @zmarkan © Zan Markan 2016 - @zmarkan
  45. • 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 © Zan Markan 2016 - @zmarkan
  46. • Espresso: Brian Legate (Flickr) • Hyperdrive: Youtube • Road

    Runner: BrownZelip (Flickr) • Back to the future: Youtube • Titanic / unit tests passing: Twitter © Zan Markan 2016 - @zmarkan