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
Espresso, Beyond the basics @ 360AnDev
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Iñaki Villar
July 14, 2017
Technology
2.3k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Espresso, Beyond the basics @ 360AnDev
Iñaki Villar
July 14, 2017
More Decks by Iñaki Villar
See All by Iñaki Villar
Scaling Android Builds in Pandemic Times
cdsap
1
180
Building Android Projects with kts
cdsap
2
300
The Build Shrugged
cdsap
1
120
State of Testing in Kotlin
cdsap
0
290
Dexs, R8 and 3.3
cdsap
0
410
Deep Dive Work Manager
cdsap
0
370
Advanced Topics Android
cdsap
0
140
Kotlin: Server-Client
cdsap
0
120
Droidcon Dubai : Kotlin - Server - Client
cdsap
0
95
Other Decks in Technology
See All in Technology
Kiroで書いた 設計書 が AI レビューの 採点基準 になる
ezaki
0
110
2026TECHFRESH畢業分享會 - Lightning Talk - 打造精準高效的 MCP 設計模式與測試實務
line_developers_tw
PRO
0
1k
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
2.9k
Disciplined Vibes: Scaling AI-Assisted Engineering
sheharyar
0
140
AIのReact習熟度を測る
uhyo
2
550
2026.06.13_AI時代に事業会社が「SIer出身エンジニア」を求める理由 / Why Businesses Seek Engineers with a System Integrator Background in the AI Era
jumtech
0
1.1k
2026 TECHFRESH 畢業分享會 - 開發日常大解密!從領域驅動到企業級上線
line_developers_tw
PRO
0
1k
【セミナー資料】Claude Code をセキュアに使うための考え方と設定の勘どころ / Claude Code Webinar 20260616
masahirokawahara
2
310
200個のGitHubリポジトリを横断調査したかった
icck
0
130
MCP Appsを作ってみよう
iwamot
PRO
4
640
LayerXにおけるセキュリティ管理の現在地と次の一手
tosho
0
180
フィジカル版Github Onshapeの紹介
shiba_8ro
0
230
Featured
See All Featured
The Pragmatic Product Professional
lauravandoore
37
7.3k
So, you think you're a good person
axbom
PRO
2
2.1k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
300
The Spectacular Lies of Maps
axbom
PRO
1
810
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
Music & Morning Musume
bryan
47
7.2k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
140
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Odyssey Design
rkendrick25
PRO
2
700
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
210
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Transcript
Beyond the basics Espresso Beyond the basics @inyaki_mwc
None
onView(withId(R.id.button_take_photo)) .perform(click()) .check(matches(isDisplayed()));
onView(withId(R.id.button_take_photo)) .perform(click()) .check(matches(isDisplayed()));
Structure
public static ViewInteraction onView(final Matcher<View> viewMatcher) { return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction();
}
BaseLayerComponent
BaseLayerModule UiControllerModule BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
ActivityLifeCycleMonitor
ActivityLifeCycleMonitor PRE_ON_CREATE, CREATED, STARTED, RESUMED, PAUSED, STOPPED, RESTARTED, DESTROYED
ActivityLifeCycleMonitor MonitoringInstrumentation
ActivityLifeCycleMonitor AndroidJUnitRunner MonitoringInstrumentation ExposedInstrumentationApi Instrumentation MonitoringInstrumentation
ActivityLifeCycleMonitor MonitoringInstrumentation mLifecycleMonitor.signalLifecycleChange(Stage.DESTROYED, activity);
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
ActivityRootLister
ActivityRootLister WindowManagerGlobal
ActivityRootLister WindowManagerGlobal mViews mParams new Root.Builder() .withDecorView(views.get(i)) .withWindowLayoutParams(params.get(i)) .build());
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
AsyncTaskPoolMonitor
@CompatAsyncTask @SdkAsyncTask AsyncTaskPoolMonitor
boolean isIdleNow() { if (!pool.getQueue().isEmpty()) { return false; } else
{ int activeCount = pool.getActiveCount(); if (0 != activeCount) { if (monitor.get() == null) { activeCount = activeCount - activeBarrierChecks.get(); } } return 0 == activeCount; } } AsyncTaskPoolMonitor
private final ThreadPoolExecutor pool; AsyncTaskPoolMonitor
private final ThreadPoolExecutor pool; MODERN_ASYNC_TASK_CLASS_NAME = “android.support.v4.content.ModernAsyncTask” MODERN_ASYNC_TASK_FIELD_NAME = "THREAD_POOL_EXECUTOR";
AsyncTaskPoolMonitor ThreadPoolExtractor
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
EventInjector
EventInjector EventInjectionStrategy
EventInjector EventInjectionStrategy InputManager injectInputEventMethod.invoke(instanceInputManagerObject,motionEvent,motionEventMode);
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
UiController
UiController boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException; boolean injectKeyEvent(KeyEvent event) throws
InjectEventSecurityException; boolean injectString(String str) throws InjectEventSecurityException;
UiController boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException; boolean injectKeyEvent(KeyEvent event) throws
InjectEventSecurityException; boolean injectString(String str) throws InjectEventSecurityException; EventInjector AsyncTaskMonitor IdlingResourceRegistry
UiController boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException; boolean injectKeyEvent(KeyEvent event) throws
InjectEventSecurityException; boolean injectString(String str) throws InjectEventSecurityException; EventInjector AsyncTaskMonitor IdlingResourceRegistry boolean allResourcesAreIdle()
ViewInteractionComponent
ViewInteractionModule Matcher<Root> ViewFinder Matcher<View> View ViewInteractionComponent
ViewInteractionModule Matcher<Root> ViewFinder Matcher<View> View ViewInteractionComponent throw new AmbiguousViewMatcherException throw
new NoMatchingViewException
ViewInteractionModule Matcher<Root> ViewFinder Matcher<View> View ViewInteractionComponent public static ViewInteraction onView(final
Matcher<View> viewMatcher) { return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction(); }
ViewInteraction
ViewInteraction public ViewInteraction perform(final ViewAction... viewActions) public ViewInteraction check(final ViewAssertion
viewAssert)
Synchronization
perform(click()); MainActivityTest
perform(click()); new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); MainActivityTest ViewActions
perform(click()); new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); MotionEvents.sendDown(uiController, coordinates, precision);
MainActivityTest ViewActions Tapper
perform(click()); new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); MotionEvents.sendDown(uiController, coordinates, precision);
uiController.injectMotionEvent(motionEvent); MainActivityTest ViewActions Tapper MotionEvents
InjectKeyEvent
InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling
InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling keyEventExecutor submit(event) loopUntil KeyInject
@Override public void loopMainThreadUntilIdle() { do { if (!asyncTaskMonitor.isIdleNow()) {
asyncTaskMonitor.notifyWhenIdle(…); condChecks.add(IdleCondition.ASYNC_TASKS_HAVE_IDLED); } if (!compatTaskMonitor.isIdleNow()) { compatTaskMonitor.notifyWhenIdle(…); condChecks.add(IdleCondition.COMPAT_TASKS_HAVE_IDLED); } if (!idlingResourceRegistry.allResourcesAreIdle()) { idlingResourceRegistry.notifyWhenAllResourcesAreIdle(…); condChecks.add(IdleCondition.DYNAMIC_TASKS_HAVE_IDLED); } loopUntil(condChecks); } while (!sIdleNow()); }
@Override public void loopMainThreadUntilIdle() { do { if (!asyncTaskMonitor.isIdleNow()) {
asyncTaskMonitor.notifyWhenIdle(…); condChecks.add(IdleCondition.ASYNC_TASKS_HAVE_IDLED); } if (!compatTaskMonitor.isIdleNow()) { compatTaskMonitor.notifyWhenIdle(…); condChecks.add(IdleCondition.COMPAT_TASKS_HAVE_IDLED); } if (!idlingResourceRegistry.allResourcesAreIdle()) { idlingResourceRegistry.notifyWhenAllResourcesAreIdle(…); condChecks.add(IdleCondition.DYNAMIC_TASKS_HAVE_IDLED); } loopUntil(condChecks); } while (!sIdleNow()); }
None
None
None
None
None
abstract class WrappingES(delegate: ExecutorService) : ExecutorService { val delegate: ExecutorService
= checkNotNull(delegate) abstract fun <T> wrapTask(callable: Callable<T>): Callable<T> fun wrapTask(command: Runnable): Runnable { val wrapped = wrapTask(Executors.callable<Any>(command, null)) return Runnable { try { wrapped.call() } catch (e: Exception) { throw RuntimeException(e) } } }
abstract class WrappingES(delegate: ExecutorService) : ExecutorService { val delegate: ExecutorService
= checkNotNull(delegate) abstract fun <T> wrapTask(callable: Callable<T>): Callable<T> fun wrapTask(command: Runnable): Runnable { val wrapped = wrapTask(Executors.callable<Any>(command, null)) return Runnable { try { wrapped.call() } catch (e: Exception) { throw RuntimeException(e) } } }
abstract class WrappingES(delegate: ExecutorService) : ExecutorService { val delegate: ExecutorService
= checkNotNull(delegate) abstract fun <T> wrapTask(callable: Callable<T>): Callable<T> fun wrapTask(command: Runnable): Runnable { val wrapped = wrapTask(Executors.callable<Any>(command, null)) return Runnable { try { wrapped.call() } catch (e: Exception) { throw RuntimeException(e) } } }
override fun execute(command: Runnable) { delegate.execute(wrapTask(command)) } override fun <T>
submit(task: Callable<T>): Future<T> { return delegate.submit(wrapTask(checkNotNull(task))) } override fun submit(task: Runnable): Future<*> { return delegate.submit(wrapTask(task)) } override fun <T> submit(task: Runnable, result: T): Future<T> { return delegate.submit(wrapTask(task), result) }
class IdlingResourceExecutorService(delegate: ExecutorService, val mIdlingResource: CountingIdlingResource) : GuavaWrappingExecutorService(delegate) { override
fun <T> wrapTask(callable: Callable<T>): Callable<T> { return WrappedCallable(callable) } private inner class WrappedCallable<T>(private val delegate: Callable<T>) : Callable<T> { @Throws(Exception::class) override fun call(): T { mIdlingResource.increment() val call: T try { call = delegate.call() } finally { mIdlingResource.decrement() } return call } } } }
class IdlingResourceExecutorService(delegate: ExecutorService, val mIdlingResource: CountingIdlingResource) : GuavaWrappingExecutorService(delegate) { override
fun <T> wrapTask(callable: Callable<T>): Callable<T> { return WrappedCallable(callable) } private inner class WrappedCallable<T>(private val delegate: Callable<T>) : Callable<T> { @Throws(Exception::class) override fun call(): T { mIdlingResource.increment() val call: T try { call = delegate.call() } finally { mIdlingResource.decrement() } return call } } } }
class IdlingResourceExecutorService(delegate: ExecutorService, val mIdlingResource: CountingIdlingResource) : GuavaWrappingExecutorService(delegate) { override
fun <T> wrapTask(callable: Callable<T>): Callable<T> { return WrappedCallable(callable) } inner class WrappedCallable<T>(val delegate: Callable<T>) : Callable<T> { @Throws(Exception::class) override fun call(): T { mIdlingResource.increment() val call: T try { call = delegate.call() } finally { mIdlingResource.decrement() } return call } } } }
None
public class RxSchedulerHook extends RxJavaSchedulersHook { private Scheduler customScheduler;
private CountingIdlingResource idling; private static RxSchedulerHook sInstance; public RxSchedulerHook(CountingIdlingResource countingIdlingResource) { FailedTest watcher = new FailedTest(); idling = countingIdlingResource; customScheduler = new Scheduler() { @Override public Scheduler.Worker createWorker() { return new CustomWorker(); } }; }
@Override public Subscription schedule(final Action0 action, final long delayTime, TimeUnit
unit) { return super.schedule(new Action0() { @Override public void call() { action.call(); } }, delayTime, unit); } @Override public Subscription schedule(final Action0 action) { return super.schedule(new Action0() { @Override public void call() { idling.increment(); try { action.call(); } finally { idling.decrement(); } } }); }
RxJavaPlugins.getInstance().registerSchedulersHook(RxSchedulerHook.get());
RxIdler
RxIdler https://github.com/square/RxIdler
Rules
@Rule public ActivityTestRule<Main> activityTestRule = new ActivityTestRule<>(Main){ @Override protected void
beforeActivityLaunched() { super.beforeActivityLaunched(); } @Override protected void afterActivityLaunched() { super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); } };
@Rule public ActivityTestRule<Main> activityTestRule = new ActivityTestRule<>(Main){ @Override protected void
beforeActivityLaunched() { super.beforeActivityLaunched(); } @Override protected void afterActivityLaunched() { super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); } };
@Rule public IntentTestRule<Main> activityTestRule = new IntentTestRule<>(Main)
@Rule public IntentTestRule<Main> activityTestRule = new IntentTestRule<>(Main) public
IntentsTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) { super(activityClass, initialTouchMode, launchActivity); } @Override protected void afterActivityLaunched() { Intents.init(); super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); Intents.release(); }
public interface TestRule { Statement apply(Statement base, Description description); }
public interface TestRule { Statement apply(Statement base, Description description); }
public class UiThreadTestRule implements TestRule { private static final String LOG_TAG = "UiThreadTestRule"; @Override public Statement apply(final Statement base, Description description) { return new UiThreadStatement(base, shouldRunOnUiThread(description)); } protected boolean shouldRunOnUiThread(Description description) { return description.getAnnotation(UiThreadTest.class) != null; } }
private class ActivityStatement extends Statement { private final Statement
mBase; public ActivityStatement(Statement base) { mBase = base; } @Override public void evaluate() throws Throwable { try { if (mLaunchActivity) { act = launchActivity(getActivityIntent()); } mBase.evaluate(); } finally { finishActivity(); } } }
public class TraceTestRule implements TestRule { private Trace trace;
@Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { trace.start(); base.evaluate(); } finally { trace.end(); } } }; } }
public class TraceTestRule implements TestRule { private Trace trace;
@Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { trace.start(); base.evaluate(); } finally { trace.end(); } } }; } } @Rule public TraceTestRule traceTestRule = new TraceTestRule(getTargetContext());
public abstract class TestWatcher implements TestRule
return new Statement() { @Override public void evaluate() throws Throwable
{ List<Throwable> errors = new ArrayList<Throwable>(); startingQuietly(description, errors); try { base.evaluate(); succeededQuietly(description, errors); } catch (AssumptionViolatedException e) { errors.add(e); skippedQuietly(e, description, errors); } catch (Throwable e) { errors.add(e); failedQuietly(e, description, errors); } finally { finishedQuietly(description, errors); } MultipleFailureException.assertEmpty(errors); } }; public abstract class TestWatcher implements TestRule
return new Statement() { @Override public void evaluate() throws Throwable
{ List<Throwable> errors = new ArrayList<Throwable>(); startingQuietly(description, errors); try { base.evaluate(); succeededQuietly(description, errors); } catch (AssumptionViolatedException e) { errors.add(e); skippedQuietly(e, description, errors); } catch (Throwable e) { errors.add(e); failedQuietly(e, description, errors); } finally { finishedQuietly(description, errors); } MultipleFailureException.assertEmpty(errors); } }; public abstract class TestWatcher implements TestRule
public class FailedTest extends TestWatcher { public FailedTest() {
uiAutomation = UiDevice.getInstance(); } @Override protected void failed(Throwable e, Description description) { super.failed(e, description); String fileNameBase = getFileNameWithoutExtension(description); saveScreenshot(fileNameBase); saveInfo(fileNameBase); } @Override protected void succeeded(Description description) { super.succeeded(description); } @Override protected void skipped(AssumptionViolatedException e, Description description) { super.skipped(e, description); }
public class FailedTest extends TestWatcher { public FailedTest() {
uiAutomation = UiDevice.getInstance(); } @Override protected void failed(Throwable e, Description description) { super.failed(e, description); String fileNameBase = getFileNameWithoutExtension(description); saveScreenshot(fileNameBase); saveInfo(fileNameBase); } @Override protected void succeeded(Description description) { super.succeeded(description); } @Override protected void skipped(AssumptionViolatedException e, Description description) { super.skipped(e, description); }
public class FailedTest extends TestWatcher { public FailedTest() {
uiAutomation = UiDevice.getInstance(); } @Override protected void failed(Throwable e, Description description) { super.failed(e, description); String fileNameBase = getFileNameWithoutExtension(description); saveScreenshot(fileNameBase); saveInfo(fileNameBase); } @Override protected void succeeded(Description description) { super.succeeded(description); } @Override protected void skipped(AssumptionViolatedException e, Description description) { super.skipped(e, description); }
public class RuleChain implements TestRule
public class RuleChain implements TestRule public RuleChain ruleChain = RuleChain.outerRule(new
LogRule("outer rule") .around(new LogRule("middle around rule") .around(new LogRule("inner around rule”))));
public class RuleChain implements TestRule public RuleChain ruleChain = RuleChain.outerRule(new
LogRule("outer rule") .around(new LogRule("middle around rule") .around(new LogRule("inner around rule”)))); Starting outer Starting middle Starting inner Finishing inner Finishing middle Finishing outer
public NewActivityTestRule<Main> activityRule = new NewActivityTestRule<>(Main.class);
public NewActivityTestRule<Main> activityRule = new NewActivityTestRule<>(Main.class); @Rule public
RuleChain chain = RuleChain.outerRule(new FailedTest() .around(activityRule);
public NewActivityTestRule<Main> activityRule = new NewActivityTestRule<>(Main.class); @Rule public
RuleChain chain = RuleChain.outerRule(new FailedTest() .around(activityRule); @Rule public RuleChain chain = RuleChain.outerRule(new FailedTest()) .around(new TraceTestRule()) .around(activityRule);
None
What’s next?
Android Test Orchestrator
Test Apk APK AndroidJunitRunner Android Test Orchestrator
Test Apk APK Orchestrator AndroidJunitRunner Android Test Orchestrator
Test Apk APK Orchestrator AndroidJunitRunner Test Test Test Android Test
Orchestrator
Multiprocess Espresso
Espresso AndroidJunitRunner Multiprocess Espresso
Espresso AndroidJunitRunner Multiprocess Espresso Espresso AndroidJunitRunner
Espresso AndroidJunitRunner Multiprocess Espresso Espresso AndroidJunitRunner
dependencies { androidTestUtil 'com.linkedin.testbutler:test-butler-app:1.3.0@apk' }
None
None
@inyaki_mwc Thanks