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
180
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
78
A Practical Introduction to CI/CD
zmarkan
0
37
The Need for Speed - Practical Tips for Optimising your CI/CD Pipeline
zmarkan
0
70
Chat app with React, Auth0, and Pusher Chatkit
zmarkan
0
360
State of Kotlin - Droidcon NYC
zmarkan
1
79
State of Kotlin - Droidcon Berlin 2018
zmarkan
1
150
Building DSLs in Kotlin for Fun and Profit
zmarkan
2
550
Push Notifications That Don't Suck
zmarkan
3
420
The State of Kotlin
zmarkan
0
190
Other Decks in Programming
See All in Programming
AIともっと楽するE2Eテスト
myohei
7
2.7k
PHPで始める振る舞い駆動開発(Behaviour-Driven Development)
ohmori_yusuke
2
400
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
1
120
#kanrk08 / 公開版 PicoRubyとマイコンでの自作トレーニング計測装置を用いたワークアウトの理想と現実
bash0c7
1
780
PicoRuby on Rails
makicamel
2
130
Quand Symfony, ApiPlatform, OpenAI et LangChain s'allient pour exploiter vos PDF : de la théorie à la production…
ahmedbhs123
0
200
なぜ「共通化」を考え、失敗を繰り返すのか
rinchoku
1
650
Systèmes distribués, pour le meilleur et pour le pire - BreizhCamp 2025 - Conférence
slecache
0
120
AIエージェントはこう育てる - GitHub Copilot Agentとチームの共進化サイクル
koboriakira
0
600
Webの外へ飛び出せ NativePHPが切り拓くPHPの未来
takuyakatsusa
2
560
Modern Angular with Signals and Signal Store:New Rules for Your Architecture @enterJS Advanced Angular Day 2025
manfredsteyer
PRO
0
220
Google Agent Development Kit でLINE Botを作ってみた
ymd65536
2
250
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
51
8.5k
Embracing the Ebb and Flow
colly
86
4.7k
Music & Morning Musume
bryan
46
6.6k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
45
7.5k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
The Straight Up "How To Draw Better" Workshop
denniskardys
235
140k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
53
2.9k
Docker and Python
trallard
44
3.5k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Measuring & Analyzing Core Web Vitals
bluesmoon
7
510
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