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 26, 2016
Programming
0
67
Android Testing Support Library: the nitty gritty
Talk delivered at Codemotion Milan 2016
Zan Markan
November 26, 2016
Tweet
Share
More Decks by Zan Markan
See All by Zan Markan
High-Performing Engineering Teams and the Holy Grail
zmarkan
0
83
A Practical Introduction to CI/CD
zmarkan
0
40
The Need for Speed - Practical Tips for Optimising your CI/CD Pipeline
zmarkan
0
75
Chat app with React, Auth0, and Pusher Chatkit
zmarkan
0
370
State of Kotlin - Droidcon NYC
zmarkan
1
87
State of Kotlin - Droidcon Berlin 2018
zmarkan
1
160
Building DSLs in Kotlin for Fun and Profit
zmarkan
2
570
Push Notifications That Don't Suck
zmarkan
3
430
The State of Kotlin
zmarkan
0
190
Other Decks in Programming
See All in Programming
Kotlinで実装するCPU/GPU 「協調的」パフォーマンス管理
matuyuhi
0
110
ボトムアップの生成AI活用を推進する社内AIエージェント開発
aku11i
0
1.3k
Software Architecture
hschwentner
6
2.4k
iOSでSVG画像を扱う
kishikawakatsumi
0
180
pnpm に provenance のダウングレード を検出する PR を出してみた
ryo_manba
1
170
AkarengaLT vol.38
hashimoto_kei
1
130
Researchlyの開発で参考にしたデザイン
adsholoko
0
100
alien-signals と自作 OSS で実現する フレームワーク非依存な ロジック共通化の探求 / Exploring Framework-Agnostic Logic Sharing with alien-signals and Custom OSS
aoseyuu
3
3.1k
Reactive Thinking with Signals and the Resource API
manfredsteyer
PRO
0
120
O Que É e Como Funciona o PHP-FPM?
marcelgsantos
0
240
Claude Agent SDK を使ってみよう
hyshu
0
1.4k
One Enishi After Another
snoozer05
PRO
0
170
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
249
1.3M
Scaling GitHub
holman
463
140k
The Cost Of JavaScript in 2023
addyosmani
55
9.1k
Faster Mobile Websites
deanohume
310
31k
The Pragmatic Product Professional
lauravandoore
36
7k
Fireside Chat
paigeccino
41
3.7k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
285
14k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Visualization
eitanlees
150
16k
VelocityConf: Rendering Performance Case Studies
addyosmani
333
24k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
For a Future-Friendly Web
brad_frost
180
10k
Transcript
© 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
"The support library is basically a mountain of hacks" —
Chris Banes, Google © 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
What else is in there? © 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
© 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) • Hyperdrive: Youtube • Road
Runner: BrownZelip (Flickr) • Back to the future: Youtube • Titanic / unit tests passing: Twitter © Zan Markan 2016 - @zmarkan