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
160
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
72
A Practical Introduction to CI/CD
zmarkan
0
31
The Need for Speed - Practical Tips for Optimising your CI/CD Pipeline
zmarkan
0
57
Chat app with React, Auth0, and Pusher Chatkit
zmarkan
0
310
State of Kotlin - Droidcon NYC
zmarkan
1
65
State of Kotlin - Droidcon Berlin 2018
zmarkan
1
140
Building DSLs in Kotlin for Fun and Profit
zmarkan
2
510
Push Notifications That Don't Suck
zmarkan
3
380
The State of Kotlin
zmarkan
0
180
Other Decks in Programming
See All in Programming
Fibonacci Function Gallery - Part 1
philipschwarz
PRO
0
220
useSyncExternalStoreを使いまくる
ssssota
6
1.1k
モバイルアプリにおける自動テストの導入戦略
ostk0069
0
110
ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12
yue4u
3
480
menu基盤チームによるGoogle Cloudの活用事例~Application Integration, Cloud Tasks編~
yoshifumi_ishikura
0
110
testcontainers のススメ
sgash708
1
120
MCP with Cloudflare Workers
yusukebe
2
220
create_tableをしただけなのに〜囚われのuuid編〜
daisukeshinoku
0
260
nekko cloudにおけるProxmox VE利用事例
irumaru
3
430
バグを見つけた?それAppleに直してもらおう!
uetyo
0
180
暇に任せてProxmoxコンソール 作ってみました
karugamo
2
720
【re:Growth 2024】 Aurora DSQL をちゃんと話します!
maroon1st
0
780
Featured
See All Featured
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
Designing for Performance
lara
604
68k
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
Being A Developer After 40
akosma
87
590k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
RailsConf 2023
tenderlove
29
940
Into the Great Unknown - MozCon
thekraken
33
1.5k
Building Better People: How to give real-time feedback that sticks.
wjessup
365
19k
A Tale of Four Properties
chriscoyier
157
23k
KATA
mclloyd
29
14k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
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