Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Espresso, Beyond the basics @ 360AnDev
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Iñaki Villar
July 14, 2017
Technology
2.4k
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
200
Building Android Projects with kts
cdsap
2
320
The Build Shrugged
cdsap
1
160
State of Testing in Kotlin
cdsap
0
300
Dexs, R8 and 3.3
cdsap
0
440
Deep Dive Work Manager
cdsap
0
400
Advanced Topics Android
cdsap
0
160
Kotlin: Server-Client
cdsap
0
130
Droidcon Dubai : Kotlin - Server - Client
cdsap
0
110
Other Decks in Technology
See All in Technology
リアクションエンジニア ― 小さなリアクションから、話しかけやすい組織をつくる ―
ryuguyana
1
130
推論基盤と Scale-Up Interconnect の現状と制約から俯瞰する技術動向
markunet
2
830
デザインレビューは自動化できるのか?─ Flutter × Figma で UI 品質保証を考える
techiro
2
810
あなたのDartコードは丸見えかもしれない ~reFlutterで暴くFlutterアプリの中身と多層防御~
zozotech
PRO
0
780
`go get`したらELFが降ってきた件
taiyow
0
180
Claude Codeで作ったアプリを安全に公開する/Built with Claude Japan 2026
yoshidashingo
0
190
攻撃 3 時間 vs 復旧 10 時間。アクセスキー流出の非対称性を体感するために CTF を作った話
kazzpapa3
1
150
AI Agent 時代のデータ基盤: ClickHouse で実現する 超高速分析とコスト削減の両立
jozono
0
200
AIコーディングはじめました
miso
0
150
視野はAIに、視座は人に / ai-for-breadth-humans-for-perspective
nkzn
6
3.2k
新卒エンジニア、 介護士になる
ryu1013
1
690
基本から分かる!AWSセキュリティ運用の考え方と任せ方
cmusudakeisuke
1
440
Featured
See All Featured
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
740
The Pragmatic Product Professional
lauravandoore
37
7.5k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
How to train your dragon (web standard)
notwaldorf
96
6.9k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.9k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.5k
Discover your Explorer Soul
emna__ayadi
2
1.3k
The Invisible Side of Design
smashingmag
301
52k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
460
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.6k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.3k
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