Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Android Testing Support Library - The Nitty Gritty
Search
Zan Markan
November 24, 2016
Programming
0
170
Android Testing Support Library - The Nitty Gritty
Talk I've given at Tokyo Android Meetup.
Zan Markan
November 24, 2016
Tweet
Share
More Decks by Zan Markan
See All by Zan Markan
High-Performing Engineering Teams and the Holy Grail
zmarkan
0
74
A Practical Introduction to CI/CD
zmarkan
0
36
The Need for Speed - Practical Tips for Optimising your CI/CD Pipeline
zmarkan
0
65
Chat app with React, Auth0, and Pusher Chatkit
zmarkan
0
350
State of Kotlin - Droidcon NYC
zmarkan
1
71
State of Kotlin - Droidcon Berlin 2018
zmarkan
1
140
Building DSLs in Kotlin for Fun and Profit
zmarkan
2
550
Push Notifications That Don't Suck
zmarkan
3
410
The State of Kotlin
zmarkan
0
180
Other Decks in Programming
See All in Programming
イベントストーミングから始めるドメイン駆動設計
jgeem
3
440
TVer iOSチームの共通認識の作り方 - Findy Job LT iOSアプリ開発の裏側 開発組織が向き合う課題とこれから
techtver
PRO
0
710
#QiitaBash TDDでAIに設計イメージを伝える
ryosukedtomita
2
1.6k
CQRS/ESのクラスとシステムフロー ~ RailsでフルスクラッチでCQRSESを組んで みたことから得た学び~
suzukimar
0
190
がんばりすぎないコーディングルール運用術
tsukakei
1
180
テスト分析入門/Test Analysis Tutorial
goyoki
12
2.7k
Rethinking Data Access: The New httpResource in Angular
manfredsteyer
PRO
0
220
Perlで痩せる
yuukis
1
660
バランスを見極めよう!実装の意味を明示するための型定義 TSKaigi 2025 Day2 (5/24)
whatasoda
2
780
What Spring Developers Should Know About Jakarta EE
ivargrimstad
1
620
CRUD から CQRS へ ~ 分離が可能にする柔軟性
tkawae
0
230
コードに語らせよう――自己ドキュメント化が内包する楽しさについて / Let the Code Speak
nrslib
5
1.1k
Featured
See All Featured
Stop Working from a Prison Cell
hatefulcrawdad
269
20k
Making the Leap to Tech Lead
cromwellryan
134
9.3k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
228
22k
How STYLIGHT went responsive
nonsquared
100
5.6k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
5
620
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
47
2.8k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
How to train your dragon (web standard)
notwaldorf
92
6k
The Cult of Friendly URLs
andyhume
78
6.4k
Git: the NoSQL Database
bkeepers
PRO
430
65k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
160
15k
Navigating Team Friction
lara
186
15k
Transcript
Android Testing Support Library The Nitty Gritty © Zan Markan
2016 - @zmarkan
Android Testing Support Library The Nitty Gritty © Zan Markan
2016 - @zmarkan
Testing 101 • on JVM vs on device • unit
/ integration / functional / end to end • Robolectric, Calabash, Instrumentation, Robotium, Appium © Zan Markan 2016 - @zmarkan
"Support" • Android framework vs Support libraries • Trend to
unbundle • support-v4, appcompat-v7, recyclerview, ... © Zan Markan 2016 - @zmarkan
Android Testing Support Library © Zan Markan 2016 - @zmarkan
Good Old Times... • jUnit3 syntax • Remember ActivityInstrumentationTestCase2<MainActi vit>?
© Zan Markan 2016 - @zmarkan
More jUnit3 goodness • overriding setUp() and tearDown() • testPrefixedMethods()
& test_prefixedMethods() • Ignorance Inheritance is bliss © Zan Markan 2016 - @zmarkan
Welcome to the present • jUnit4 syntax • No more
extending • @Test, @Before, @After, @AfterClass,... • ActivityTestRule, InstrumentationRegistry © Zan Markan 2016 - @zmarkan
What else is in there? • Espresso • More Espresso
(there's a lot to it) • UIAutomator • Test Runner • Test Rules • ... © Zan Markan 2016 - @zmarkan
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
Gradle • Set your test runner to be AndroidJUnitRunner •
Add dependencies • Voila! © Zan Markan 2016 - @zmarkan
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
Gradle • Set your test runner to be AndroidJUnitRunner •
Add dependencies • Voila! • Resolve dependencies © Zan Markan 2016 - @zmarkan
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
Resolve with • Force dependency versions in the test APK
• exclude dependency (everywhere applicable) • use Resolution strategy © Zan Markan 2016 - @zmarkan
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
Gradle • Set your test runner to be AndroidJUnitRunner •
Add dependencies • Resolve dependencies • Voila! © Zan Markan 2016 - @zmarkan
© Zan Markan 2016 - @zmarkan
Espresso - components • View interactions & assertions • Hamcrest
syntax • No (unnecessary) waits © Zan Markan 2016 - @zmarkan
Espresso - API Cheat sheet: google.github.io/android-testing-support-library/docs/ espresso/cheatsheet © Zan Markan
2016 - @zmarkan
Poking the screen onView(withId(R.id.button)).perform(click()); © Zan Markan 2016 - @zmarkan
Poking the screen onView(withId(R.id.button)).perform(click()); • allOf, anyOf, ... • withParent,
withText... • isDisplayed, isDialog... © Zan Markan 2016 - @zmarkan
Espresso - contrib • RecyclerView • Drawers • Pickers •
Accessibility © Zan Markan 2016 - @zmarkan
Espresso++ • Custom matchers • Custom ViewActions • Idling resources
• Page objects • Intent mocking © Zan Markan 2016 - @zmarkan
Custom matchers • Find a view in the hierarchy •
Good for custom views & components • Override: • describeTo • matchesSafely © Zan Markan 2016 - @zmarkan
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
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
Custom ViewActions - API public class ScrollableUtils { public static
ViewAction scrollableScrollTo() { return actionWithAssertions(new ScrollableScrollToAction()); } } © Zan Markan 2016 - @zmarkan
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
Implementation... @Override public Matcher<View> getConstraints() { return allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), isDescendantOfA(anyOf( isAssignableFromClassOrInterface(ScrollingView.class))));
} © Zan Markan 2016 - @zmarkan
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
Don't panic. • Take something that works • ...like scrollTo()
in regular Espresso for ScrollView • modify it • profit © Zan Markan 2016 - @zmarkan
Custom IdlingResource • A better way to wait • Use
when you have background stuff going on • Override: • getName • isIdleNow • registerIdleTransitionCallback © Zan Markan 2016 - @zmarkan
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
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
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
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
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
To boldly go... © Zan Markan 2016 - @zmarkan
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
UI Automator viewer © Zan Markan 2016 - @zmarkan
UI Automator syntax UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation(); device.pressHome(); device.findObject(new UiSelector().descriptionContains("Google
search")).clickAndWaitForNewWindow(); © Zan Markan 2016 - @zmarkan
Test Runner © Zan Markan 2016 - @zmarkan
Test Runner • Spins up tests... • and runs them
• Customise to prepare mocks • Easier run/debug via command line © Zan Markan 2016 - @zmarkan
Extending the Runner • Use custom Application class • Provide
mocked dependencies • Specify this new runner in build.gradle • Kotlin Test Runner © Zan Markan 2016 - @zmarkan
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
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
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
Test Rules © Zan Markan 2016 - @zmarkan
Test Rules • Set starting activity / Service • Replace
ActivityInstrumentationTestCase2 • (in most cases) • Add / Extend to create more components © Zan Markan 2016 - @zmarkan
Test Rules - examples • MockWebServerRule - sets up MockWebServer
when required • Source: github.com/artem-zinnatullin/ qualitymatters © Zan Markan 2016 - @zmarkan
© Zan Markan 2016 - @zmarkan
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
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
Above & Beyond? • Espresso Web for WebViews • JankTestHelper
• Stuff is being added. © Zan Markan 2016 - @zmarkan
Best of all? © Zan Markan 2016 - @zmarkan
It's all open source! © Zan Markan 2016 - @zmarkan
Pusher /ˈpʊʃ ər/ noun 1. Platform of APIs for building
highly connected apps 2. Hiring in London ! © Zan Markan 2016 - @zmarkan
fin • ! www.spacecowboyrocketcompany.com • " @zmarkan • # zan
at markan dot me • $ androidchat.co (zmarkan) • %& @zmarkan © Zan Markan 2016 - @zmarkan
• 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
• Espresso: Brian Legate (Flickr) • USS Enterprise: Randomwallpapers.net •
Road Runner: BrownZelip (Flickr) • Back to the future: Youtube © Zan Markan 2016 - @zmarkan