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
攻撃者視点で考えるDetection Engineering
cryptopeg
3
1.8k
エラーバジェットのアラートのタイミングを考える.pdf
kairim0
0
150
How Timee Delivers Day 1 Production Ready LLM Features
tomoyks
0
230
現地で盛り上がった WWDC26 Keynote
zozotech
PRO
1
250
AIソロプレナー時代に2ヶ月で20人増員した事業創造会社の開発組織の話
miyatakoji
0
660
中期計画、2回作ってみた ~業務委託と正社員、両方の視点から~
demaecan
1
750
なぜ Platform Engineering の土台に Kubernetes を選ぶのか
r4ynode
2
640
NAB Show 2026 動画技術関連レポート / NAB Show 2026 Report
cyberagentdevelopers
PRO
0
200
SONiCのLinuxベースを活かしたZabbix監視
sonic
0
160
アンオフィシャルな、オフィシャルからのお願い
wyamazak_devrel
0
110
フィジカル版Github Onshapeの紹介
shiba_8ro
0
220
RAG を使わないという選択肢
tatsutaka
1
230
Featured
See All Featured
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.4k
Designing for Performance
lara
611
70k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
What's in a price? How to price your products and services
michaelherold
247
13k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
RailsConf 2023
tenderlove
30
1.5k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
440
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
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