Slide 1

Slide 1 text

Android Testing Support Library The Nitty Gritty © Zan Markan 2016 - @zmarkan

Slide 2

Slide 2 text

Android Testing Support Library The Nitty Gritty © Zan Markan 2016 - @zmarkan

Slide 3

Slide 3 text

Testing 101 • on JVM vs on device • unit / integration / functional / end to end • Robolectric, Calabash, Instrumentation, Robotium, Appium © Zan Markan 2016 - @zmarkan

Slide 4

Slide 4 text

"Support" • Android framework vs Support libraries • Trend to unbundle • support-v4, appcompat-v7, recyclerview, ... © Zan Markan 2016 - @zmarkan

Slide 5

Slide 5 text

Android Testing Support Library © Zan Markan 2016 - @zmarkan

Slide 6

Slide 6 text

Good Old Times... • jUnit3 syntax • Remember ActivityInstrumentationTestCase2? © Zan Markan 2016 - @zmarkan

Slide 7

Slide 7 text

More jUnit3 goodness • overriding setUp() and tearDown() • testPrefixedMethods() & test_prefixedMethods() • Ignorance Inheritance is bliss © Zan Markan 2016 - @zmarkan

Slide 8

Slide 8 text

Welcome to the present • jUnit4 syntax • No more extending • @Test, @Before, @After, @AfterClass,... • ActivityTestRule, InstrumentationRegistry © Zan Markan 2016 - @zmarkan

Slide 9

Slide 9 text

What else is in there? • Espresso • More Espresso (there's a lot to it) • UIAutomator • Test Runner • Test Rules • ... © Zan Markan 2016 - @zmarkan

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Gradle • Set your test runner to be AndroidJUnitRunner • Add dependencies • Voila! © Zan Markan 2016 - @zmarkan

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Gradle • Set your test runner to be AndroidJUnitRunner • Add dependencies • Voila! • Resolve dependencies © Zan Markan 2016 - @zmarkan

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Resolve with • Force dependency versions in the test APK • exclude dependency (everywhere applicable) • use Resolution strategy © Zan Markan 2016 - @zmarkan

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Gradle • Set your test runner to be AndroidJUnitRunner • Add dependencies • Resolve dependencies • Voila! © Zan Markan 2016 - @zmarkan

Slide 18

Slide 18 text

© Zan Markan 2016 - @zmarkan

Slide 19

Slide 19 text

Espresso - components • View interactions & assertions • Hamcrest syntax • No (unnecessary) waits © Zan Markan 2016 - @zmarkan

Slide 20

Slide 20 text

Espresso - API Cheat sheet: google.github.io/android-testing-support-library/docs/ espresso/cheatsheet © Zan Markan 2016 - @zmarkan

Slide 21

Slide 21 text

Poking the screen onView(withId(R.id.button)).perform(click()); © Zan Markan 2016 - @zmarkan

Slide 22

Slide 22 text

Poking the screen onView(withId(R.id.button)).perform(click()); • allOf, anyOf, ... • withParent, withText... • isDisplayed, isDialog... © Zan Markan 2016 - @zmarkan

Slide 23

Slide 23 text

Espresso - contrib • RecyclerView • Drawers • Pickers • Accessibility © Zan Markan 2016 - @zmarkan

Slide 24

Slide 24 text

Espresso++ • Custom matchers • Custom ViewActions • Idling resources • Page objects • Intent mocking © Zan Markan 2016 - @zmarkan

Slide 25

Slide 25 text

Custom matchers • Find a view in the hierarchy • Good for custom views & components • Override: • describeTo • matchesSafely © Zan Markan 2016 - @zmarkan

Slide 26

Slide 26 text

Custom matchers interface MyView{ boolean hasCustomProperty(); } static BoundedMatcher withCustomPropery() { return new BoundedMatcher(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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Custom ViewActions - API public class ScrollableUtils { public static ViewAction scrollableScrollTo() { return actionWithAssertions(new ScrollableScrollToAction()); } } © Zan Markan 2016 - @zmarkan

Slide 29

Slide 29 text

Implementation... public class ScrollableScrollToAction implements ViewAction{ private static final String TAG = com.zmarkan.nestedscroll.action.ScrollableScrollToAction.class.getSimpleName(); @SuppressWarnings("unchecked") @Override public Matcher 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

Slide 30

Slide 30 text

Implementation... @Override public Matcher getConstraints() { return allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), isDescendantOfA(anyOf( isAssignableFromClassOrInterface(ScrollingView.class)))); } © Zan Markan 2016 - @zmarkan

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Don't panic. • Take something that works • ...like scrollTo() in regular Espresso for ScrollView • modify it • profit © Zan Markan 2016 - @zmarkan

Slide 33

Slide 33 text

Custom IdlingResource • A better way to wait • Use when you have background stuff going on • Override: • getName • isIdleNow • registerIdleTransitionCallback © Zan Markan 2016 - @zmarkan

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

To boldly go... © Zan Markan 2016 - @zmarkan

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

UI Automator viewer © Zan Markan 2016 - @zmarkan

Slide 42

Slide 42 text

UI Automator syntax UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation(); device.pressHome(); device.findObject(new UiSelector().descriptionContains("Google search")).clickAndWaitForNewWindow(); © Zan Markan 2016 - @zmarkan

Slide 43

Slide 43 text

Test Runner © Zan Markan 2016 - @zmarkan

Slide 44

Slide 44 text

Test Runner • Spins up tests... • and runs them • Customise to prepare mocks • Easier run/debug via command line © Zan Markan 2016 - @zmarkan

Slide 45

Slide 45 text

Extending the Runner • Use custom Application class • Provide mocked dependencies • Specify this new runner in build.gradle • Kotlin Test Runner © Zan Markan 2016 - @zmarkan

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Test Rules © Zan Markan 2016 - @zmarkan

Slide 50

Slide 50 text

Test Rules • Set starting activity / Service • Replace ActivityInstrumentationTestCase2 • (in most cases) • Add / Extend to create more components © Zan Markan 2016 - @zmarkan

Slide 51

Slide 51 text

Test Rules - examples • MockWebServerRule - sets up MockWebServer when required • Source: github.com/artem-zinnatullin/ qualitymatters © Zan Markan 2016 - @zmarkan

Slide 52

Slide 52 text

© Zan Markan 2016 - @zmarkan

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Above & Beyond? • Espresso Web for WebViews • JankTestHelper • Stuff is being added. © Zan Markan 2016 - @zmarkan

Slide 56

Slide 56 text

Best of all? © Zan Markan 2016 - @zmarkan

Slide 57

Slide 57 text

It's all open source! © Zan Markan 2016 - @zmarkan

Slide 58

Slide 58 text

Pusher /ˈpʊʃ ər/ noun 1. Platform of APIs for building highly connected apps 2. Hiring in London ! © Zan Markan 2016 - @zmarkan

Slide 59

Slide 59 text

fin • ! www.spacecowboyrocketcompany.com • " @zmarkan • # zan at markan dot me • $ androidchat.co (zmarkan) • %& @zmarkan © Zan Markan 2016 - @zmarkan

Slide 60

Slide 60 text

• 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

Slide 61

Slide 61 text

• Espresso: Brian Legate (Flickr) • USS Enterprise: Randomwallpapers.net • Road Runner: BrownZelip (Flickr) • Back to the future: Youtube © Zan Markan 2016 - @zmarkan