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
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
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
420
Deep Dive Work Manager
cdsap
0
380
Advanced Topics Android
cdsap
0
150
Kotlin: Server-Client
cdsap
0
120
Droidcon Dubai : Kotlin - Server - Client
cdsap
0
98
Other Decks in Technology
See All in Technology
もう一度考える SRE チームの作り方・育て方 / Rethinking SRE #1: Building and Growing SRE Teams
rrreeeyyy
4
620
LangfuseによるLLMOps基盤の構築と活用事例
zozotech
PRO
1
220
データ組織の転換期 一足飛びしない段階的戦略
leveragestech
PRO
0
140
AIツールを導入しても生産性はあがらない? カオナビが直面した 3つの壁と乗り越え方。/ Overcoming 3 Barriers to AI-Driven Productivity at kaonavi
kaonavi
0
1.3k
テックカンファレンス三大ステークホルダーの文化人類学 ─ 違いを認め合う関係性作り
bash0c7
5
1.5k
PLaMo 3.0 Primeの構造化出力サポート
pfn
PRO
0
160
AI Agent を本番環境へ―― Microsoft Foundry × Azure Serverless で作る Enterprise-Ready な基盤
shibayan
PRO
1
1k
最高のシステムプロンプトを作るためにフィードバック機能を導入した話
alchemy1115
1
270
オートロックマンションなのに、各部屋は施錠なし!? 攻撃者が組織内ネットワークで大暴れする理由 / The Front Door Is Locked, but the Rooms Are Wide Open: Why Attackers Move Freely Inside Enterprise Networks
nttcom
0
680
【5分でわかる】セーフィー エンジニア向け会社紹介
safie_recruit
0
53k
「待ち時間」の消滅と「自我消耗」の加速:生成AI時代のエンジニアを救うメンタル・リソース管理
poropinai1966
0
400
20260608_Codexの可能性_ノンプログラマー向け_大城追記
doradora09
PRO
0
720
Featured
See All Featured
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
240
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
[SF Ruby Conf 2025] Rails X
palkan
2
1.2k
Product Roadmaps are Hard
iamctodd
55
12k
Become a Pro
speakerdeck
PRO
31
6k
How to train your dragon (web standard)
notwaldorf
97
6.7k
Into the Great Unknown - MozCon
thekraken
41
2.6k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
230
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
230
Leo the Paperboy
mayatellez
8
2k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.2k
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