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
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
310
The Build Shrugged
cdsap
1
130
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
97
Other Decks in Technology
See All in Technology
プロンプト_きのこカンファレンス2026_LT
yurufuwahealer
0
140
クラウド上のデータ復旧で見落としがちな制約: 医療系 SaaS の BCP 設計から得た教訓
kakehashi
PRO
0
2.5k
cccccc
moznion
0
1.8k
認証認可だけじゃない! ID管理の構成要素と ライフサイクルを意識しよう
ritou
1
520
20260702_生成AIはどこまで成長するのか_チャットだけじゃない世界
doradora09
PRO
0
110
地域 SRE コミュニティ最前線 / SRE NEXT 2026 Discussion Night Track C
muziyoshiz
0
160
Road to SRE NEXTの今までとこれから
hiroyaonoe
0
220
そのタスクオンスケですか?
poropinai1966
0
140
Why is RC4 still being used?
tamaiyutaro
0
310
攻撃者がいなくてもAIエージェントはインシデントを起こす
nomizone
0
200
NDIAS CTF 2026 問題解説会資料
bata_24
0
180
CSに"SLO"は要らない、経営層に"99.9%"は伝わらない - SREを全社に"翻訳"する3原則
cscengineer
PRO
1
3.6k
Featured
See All Featured
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
260
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
Building AI with AI
inesmontani
PRO
1
1.1k
How GitHub (no longer) Works
holman
316
150k
Making Projects Easy
brettharned
120
6.7k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.5k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
230
The Curious Case for Waylosing
cassininazir
1
430
Tell your own story through comics
letsgokoyo
1
990
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
180
Documentation Writing (for coders)
carmenintech
77
5.4k
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