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
190
Building Android Projects with kts
cdsap
2
310
The Build Shrugged
cdsap
1
150
State of Testing in Kotlin
cdsap
0
300
Dexs, R8 and 3.3
cdsap
0
440
Deep Dive Work Manager
cdsap
0
390
Advanced Topics Android
cdsap
0
150
Kotlin: Server-Client
cdsap
0
130
Droidcon Dubai : Kotlin - Server - Client
cdsap
0
100
Other Decks in Technology
See All in Technology
ミイダス株式会社 テックチームのご紹介 / MIIDAS Tech Team
miidas
0
170
LLM機能を自作して分かるSnowflake Cortex AIの強み
nayuts
0
320
DWH の限界突破! AI エージェント向け爆速リアルタイムデータ基盤 ClickHouse
jozono
1
260
不可逆な意思決定を避けながら10億超の顧客データを AI Ready にするデータ基盤設計
baseballyama
1
160
Google Cloud Next Tokyo 26登壇時のスクリプト
recruitengineers
PRO
0
210
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
17
120k
実体験から学ぶ分析エージェントの開発と運⽤
recruitengineers
PRO
4
1.3k
IoTデバイスを繋げるプロトコルMatterをAndroidで使用する方法
harutiro
0
120
20260930_Gemma4_Hands-on
tsho
0
240
LocalStack を使ったサーバーレスアプリケーション開発 / Serverless Development with LocalStack
kakakakakku
1
290
MCPゲートウェイを作って運用してわかったこと — Agent時代の権限管理の現在地
mtpooh
10
3.2k
無理せず続ける、子育てエンジニアの学び
codmoninc
PRO
0
410
Featured
See All Featured
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
910
GraphQLとの向き合い方2022年版
quramy
50
15k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.4k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
The Invisible Side of Design
smashingmag
301
52k
Docker and Python
trallard
47
4.2k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
700
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
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