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
Search
Iñaki Villar
October 23, 2016
Technology
8.4k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Espresso, Beyond the basics
Iñaki Villar
October 23, 2016
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
推論基盤と Scale-Up Interconnect の現状と制約から俯瞰する技術動向
markunet
2
840
BALLAS Technology Deck
ballas_inc
0
160
Autonomous AI Lakehouse - Live AI Hubから始めるセキュアなAIデータ活用 -
oracle4engineer
PRO
3
200
Active Directory と Entra ID のむむむな関係
murachiakira
PRO
3
1.7k
An Introduction to Context Ontology Accelerator (teaching AI agents what your data means)
inariku
PRO
1
110
"AWSはちょっと苦手"でも大丈夫。AWS Blocks で手軽に作る Flutter のバックエンド #FlutterKaigi
tacck
PRO
0
160
バックオフィスに染み出すTypeScript ── clasp で安全に仕組みを作る
nealle
1
180
Gifu WordPress Meetup #200 に向けて、私たちには何ができるだろうか?/ 2026-11-07 Gifu WordPress Meetup
torounit
0
210
【dbts2026】AIエージェントの 判断と探索を変える、 データベース エンジニアリング― 既存DBの処理と依存関係から作る、オペレーショナルオントロジー ―
shisyu_gaku
0
230
公共システムAdventCalendar2026
toru_kubota
0
280
Content Mapper APIの応用例~CSS Modules、言語ファイル~
uhyo
2
620
業務でAWSをあまり使っていない私が、AWSへのモチベを保つ方法
smt7174
0
170
Featured
See All Featured
Embracing the Ebb and Flow
colly
88
5.2k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3.1k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
380
Design in an AI World
tapps
1
380
Reflections from 52 weeks, 52 projects
jeffersonlam
356
22k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
370
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
880
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
121
120k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.3k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
70
68k
Un-Boring Meetings
codingconduct
0
490
Chasing Engaging Ingredients in Design
codingconduct
0
390
Transcript
Espresso Beyond the basics
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
Instrumentation
Instrumentation MonitorningInstrumentation
Instrumentation MonitorningInstrumentation AndroidJunitRunner
Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards
shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle
Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards
shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle
Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards
shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle
Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards
shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle
Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards
shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle
None
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
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler UiController BaseLayerComponent
BaseLayerComponent
FailureHandlerHolder BaseLayerComponent FailureHandler ActivityRootLister IdlingResourceRegistry ViewInteractionComponent
FailureHandlerHolder BaseLayerComponent FailureHandler ActivityRootLister ViewInteractionComponent ViewInteractionModule IdlingResourceRegistry
FailureHandlerHolder BaseLayerComponent FailureHandler ActivityRootLister ViewInteractionComponent ViewInteractionModule Matcher<Root> Matcher<View> ViewFinder View
IdlingResourceRegistry
None
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 loopMainThreadUntilIdle
InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling
InjectKeyEvent loopMainThreadUntilIdle keyEventExecutor submit(event) loopUntil loopUntil Asynctask Idling KeyInject
@Override public void loopMainThreadUntilIdle() { do { EnumSet<IdleCondition> condChecks =
EnumSet.noneOf(IdleCondition.class); 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 (!asyncTaskMonitor.isIdleNow() || !compatTaskMonitor.isIdleNow()() || !idlingResourceRegistry.allResourcesAreIdle()); } UiControllerImpl.java
@Override public void loopMainThreadUntilIdle() { do { EnumSet<IdleCondition> condChecks =
EnumSet.noneOf(IdleCondition.class); 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 (!asyncTaskMonitor.isIdleNow() || !compatIdle() || !idlingResourceRegistry.allResourcesAreIdle()); } UiControllerImpl.java
@Override public void loopMainThreadUntilIdle() { do { EnumSet<IdleCondition> condChecks =
EnumSet.noneOf(IdleCondition.class); 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 (!asyncTaskMonitor.isIdleNow() || !compatIdle() || !idlingResourceRegistry.allResourcesAreIdle()); } UiControllerImpl.java
AsyncTaskPoolMonitor
AsyncTaskPoolMonitor AsyncTask CompatAsyncTask
private final AtomicReference<IdleMonitor> monitor = new AtomicReference<IdleMonitor>(null); private final ThreadPoolExecutor
pool; private final AtomicInteger activeBarrierChecks = new AtomicInteger(0); AsyncTaskPoolMonitor.java
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; } } private final AtomicReference<IdleMonitor> monitor = new AtomicReference<IdleMonitor>(null); private final ThreadPoolExecutor pool; private final AtomicInteger activeBarrierChecks = new AtomicInteger(0); AsyncTaskPoolMonitor.java
IdlingResourceRegistry.java
public boolean registerResources(final List<? extends IdlingResource> resourceList) { } public
boolean unregisterResources(final List<? extends IdlingResource> resourceList) { } IdlingResourceRegistry.java
boolean allResourcesAreIdle() { for (int i = idleState.nextSetBit(0); i >=
0 && i < resources.size(); i = idleState.nextSetBit(i + 1)) { idleState.set(i, resources.get(i).isIdleNow()); } return idleState.cardinality() == resources.size(); } public boolean registerResources(final List<? extends IdlingResource> resourceList) { } public boolean unregisterResources(final List<? extends IdlingResource> resourceList) { } IdlingResourceRegistry.java
None
abstract class WrappingExecutorService implements ExecutorService { private final ExecutorService delegate;
protected GuavaWrappingExecutorService(ExecutorService delegate) { this.delegate = checkNotNull(delegate); } protected abstract <T> Callable<T> wrapTask(Callable<T> callable); protected Runnable wrapTask(Runnable command) { final Callable<Object> wrapped = wrapTask(Executors.callable(command, null)); return new Runnable() { @Override public void run() { try { wrapped.call(); } catch (Exception e) { } } }; } @Override public final void execute(Runnable command) { delegate.execute(wrapTask(command)); } @Override public final <T> Future<T> submit(Callable<T> task) { return delegate.submit(wrapTask(checkNotNull(task))); } @Override public final <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { return delegate.invokeAll(wrapTasks(tasks)); } } Guava
public class IdlingResourceExecutorService extends GuavaWrappingExecutorService { private final CountingIdlingResource
mIdlingResource; public IdlingResourceExecutorService(ExecutorService delegate, CountingIdlingResource idlingResource) { super(delegate); mIdlingResource = idlingResource; } @Override protected <T> Callable<T> wrapTask(Callable<T> callable) { return new WrappedCallable<>(callable); } private final class WrappedCallable<T> implements Callable<T> { private final Callable<T> delegate; public WrappedCallable(Callable<T> delegate) { this.delegate = delegate; } @Override public T call() throws Exception { mIdlingResource.increment(); T call; try { call = delegate.call(); } finally { mIdlingResource.decrement(); } return call; } } }
public class IdlingResourceExecutorService extends GuavaWrappingExecutorService { private final CountingIdlingResource
mIdlingResource; public IdlingResourceExecutorService(ExecutorService delegate, CountingIdlingResource idlingResource) { super(delegate); mIdlingResource = idlingResource; } @Override protected <T> Callable<T> wrapTask(Callable<T> callable) { return new WrappedCallable<>(callable); } private final class WrappedCallable<T> implements Callable<T> { private final Callable<T> delegate; public WrappedCallable(Callable<T> delegate) { this.delegate = delegate; } @Override public T call() throws Exception { mIdlingResource.increment(); T call; try { call = delegate.call(); } finally { mIdlingResource.decrement(); } return call; } } }
public class IdlingResourceExecutorService extends GuavaWrappingExecutorService { private final CountingIdlingResource
mIdlingResource; public IdlingResourceExecutorService(ExecutorService delegate, CountingIdlingResource idlingResource) { super(delegate); mIdlingResource = idlingResource; } @Override protected <T> Callable<T> wrapTask(Callable<T> callable) { return new WrappedCallable<>(callable); } private final class WrappedCallable<T> implements Callable<T> { private final Callable<T> delegate; public WrappedCallable(Callable<T> delegate) { this.delegate = delegate; } @Override public T call() throws Exception { mIdlingResource.increment(); T call; try { call = delegate.call(); } finally { mIdlingResource.decrement(); } return call; } } }
RxJava
RxJava 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(); } } }); } RxJava
RxJava RxJavaPlugins.getInstance().registerSchedulersHook(RxSchedulerHook.get());
OkHttp3IdlingResource
@Before public void setUp() { OkHttpClient client = new OkHttpClient();
IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", client); Espresso.registerIdlingResources(resource); } OkHttp3IdlingResource
@Before public void setUp() { OkHttpClient client = new OkHttpClient();
IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", client); Espresso.registerIdlingResources(resource); } private OkHttp3IdlingResource(String name, Dispatcher dispatcher) { this.name = name; this.dispatcher = dispatcher; dispatcher.setIdleCallback(new Runnable() { public void run() { ResourceCallback callback = OkHttp3IdlingResource.this.callback; if(callback != null) { callback.onTransitionToIdle(); } } }); } public boolean isIdleNow() { return this.dispatcher.runningCallsCount() == 0; } OkHttp3IdlingResource
Rules
@Rule public ActivityTestRule<Main> activityTestRule = new ActivityTestRule<>(Main) ActivityTestRule
@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(); } }; ActivityTestRule
@Rule public IntentTestRule<Main> activityTestRule = new IntentTestRule<>(Main) IntentTestRule
@Rule public IntentTestRule<Main> activityTestRule = new IntentTestRule<>(Main) public IntentsTestRule(Class<T> activityClass)
{ super(activityClass); } public IntentsTestRule(Class<T> activityClass, boolean initialTouchMode) { super(activityClass, initialTouchMode); } 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(); } IntentTestRule
public class CustomActivityTestRule<T extends Activity> extends ActivityTestRule<T> CustomActivityTestRule
public class CustomActivityTestRule<T extends Activity> extends ActivityTestRule<T> { public
CustomActivityTestRule(Class<T> activityClass) { super(activityClass); } public CustomActivityTestRule(Class<T> activityClass, boolean initialTouchMode) { super(activityClass, initialTouchMode); } @Override protected void afterActivityLaunched() { Intents.init(); super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); Intents.release(); } CustomActivityTestRule
public interface TestRule { Statement apply(Statement base, Description description); }
TestRule
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; } } TestRule
private class ActivityStatement extends Statement { private final Statement
mBase; public ActivityStatement(Statement base) { mBase = base; } @Override public void evaluate() throws Throwable { try { if (mLaunchActivity) { mActivity = launchActivity(getActivityIntent()); } mBase.evaluate(); } finally { finishActivity(); } } } TestRule
public class TraceTestRule implements TestRule { private Trace trace;
public TraceTestRule(Context context) { this.trace = new Trace(context); } @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()); TestRule
public abstract class TestWatcher implements TestRule TestWatcher
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 (@SuppressWarnings("deprecation") 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 TestWatcher
public class FailedTest extends TestWatcher { TestWatcher
public class FailedTest extends TestWatcher { public FailedTest() {
uiAutomation = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); } @Override protected void failed(Throwable e, Description description) { super.failed(e, description); String fileNameBase = getFileNameWithoutExtension(description); saveScreenshot(fileNameBase); saveInfo(fileNameBase); } TestWatcher
public class FailedTest extends TestWatcher { public FailedTest() {
uiAutomation = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); } @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); } TestWatcher
public class RuleChain implements TestRule{ RuleChain
public class RuleChain implements TestRule public RuleChain ruleChain = RuleChain.outerRule(new
LoggingRule("outer rule") .around(new LoggingRule("middle around rule") .around(new LoggingRule("inner around rule”)))); RuleChain
public class RuleChain implements TestRule{ public RuleChain ruleChain = RuleChain.outerRule(new
LoggingRule("outer rule") .around(new LoggingRule("middle around rule") .around(new LoggingRule("inner around rule”)))); Starting outer rule Starting middle rule Starting inner rule Finishing inner rule Finishing middle rule Finishing outer rule RuleChain
public CustomActivityTestRule<Main> activityRule = new CustomActivityTestRule<>(Main.class);
public CustomActivityTestRule<Main> activityRule = new CustomActivityTestRule<>(Main.class); @Rule public
RuleChain chain = RuleChain.outerRule(new FailedTest() .around(activityRule);
public CustomActivityTestRule<Main> activityRule = new CustomActivityTestRule<>(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);
public class Device { public Device() { } private
static boolean deviceEquals(String device) { return Build.DEVICE.equals(device); } public static class Genymotion implements Condition { public Genymotion() { } public boolean isSatisfied() { return Device.deviceEquals("vbox86p"); } } } AndroidTestRules
Permissions
None
@Before public void grantPhonePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand( "pm grant " + getTargetContext().getPackageName() + " android.permission.CALL_PHONE"); } }
afterEvaluate { tasks.each { task -> if (task.name.startsWith('connectedAndroidTest')) { task.dependsOn
grantAnimationPermissions } } }
afterEvaluate { tasks.each { task -> if (task.name.startsWith('connectedAndroidTest')) { task.dependsOn
grantAnimationPermissions } } } devices=$($adb devices | grep -v 'List of devices' | cut -f1 | grep '.') for device in $devices; do echo "Setting permissions to device" $device "for package" $package $adb -s $device shell pm grant $package android.permission.xxxx done
None
ATSL Spoon Fork TestButler Burst RxPresso AndroidTestRules
Anton Batishchev Anton Malinskiy Chiu-ki Chan Erik Hellman Friedger Müffke
Iordanis Giannakakis Jake Wharton Jose Alcérreca Michael Bailey Paul Blundell Stephan Linzner Valera Zakharov Xavi Rigau ATSL Spoon Fork TestButler Burst RxPresso AndroidTestRules
Thanks
+IñakiVillar @inyaki_mwc Thanks