Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Espresso, Beyond the basics @ 360AnDev

Espresso, Beyond the basics @ 360AnDev

Iñaki Villar

July 14, 2017
Tweet

More Decks by Iñaki Villar

Other Decks in Technology

Transcript

  1. Beyond the basics
    Espresso
    Beyond the basics
    @inyaki_mwc

    View Slide

  2. View Slide

  3. onView(withId(R.id.button_take_photo))

    .perform(click())

    .check(matches(isDisplayed()));


    View Slide

  4. onView(withId(R.id.button_take_photo))

    .perform(click())

    .check(matches(isDisplayed()));


    View Slide

  5. Structure

    View Slide

  6. public static ViewInteraction onView(final Matcher viewMatcher) {

    return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction();

    }

    View Slide

  7. BaseLayerComponent

    View Slide

  8. BaseLayerModule
    UiControllerModule
    BaseLayerComponent

    View Slide

  9. BaseLayerModule ActivityLifeCycleMonitor
    Context
    Looper
    AsyncTaskPoolMonitor
    Executor
    ActivityRootLister
    EventInjector
    FailureHandler
    UiControllerModule
    Recycler
    BaseLayerComponent
    UiController

    View Slide

  10. BaseLayerModule ActivityLifeCycleMonitor
    Context
    Looper
    AsyncTaskPoolMonitor
    Executor
    ActivityRootLister
    EventInjector
    FailureHandler
    UiControllerModule
    Recycler
    BaseLayerComponent
    UiController

    View Slide

  11. ActivityLifeCycleMonitor

    View Slide

  12. ActivityLifeCycleMonitor
    PRE_ON_CREATE,
    CREATED,
    STARTED,
    RESUMED,
    PAUSED,
    STOPPED,
    RESTARTED,
    DESTROYED

    View Slide

  13. ActivityLifeCycleMonitor
    MonitoringInstrumentation

    View Slide

  14. ActivityLifeCycleMonitor
    AndroidJUnitRunner
    MonitoringInstrumentation
    ExposedInstrumentationApi
    Instrumentation
    MonitoringInstrumentation

    View Slide

  15. ActivityLifeCycleMonitor
    MonitoringInstrumentation
    mLifecycleMonitor.signalLifecycleChange(Stage.DESTROYED, activity);

    View Slide

  16. BaseLayerModule ActivityLifeCycleMonitor
    Context
    Looper
    AsyncTaskPoolMonitor
    Executor
    ActivityRootLister
    EventInjector
    FailureHandler
    UiControllerModule
    Recycler
    BaseLayerComponent
    UiController

    View Slide

  17. ActivityRootLister

    View Slide

  18. ActivityRootLister
    WindowManagerGlobal

    View Slide

  19. ActivityRootLister
    WindowManagerGlobal
    mViews
    mParams
    new Root.Builder()
    .withDecorView(views.get(i))
    .withWindowLayoutParams(params.get(i))
    .build());

    View Slide

  20. BaseLayerModule ActivityLifeCycleMonitor
    Context
    Looper
    AsyncTaskPoolMonitor
    Executor
    ActivityRootLister
    EventInjector
    FailureHandler
    UiControllerModule
    Recycler
    BaseLayerComponent
    UiController

    View Slide

  21. AsyncTaskPoolMonitor

    View Slide

  22. @CompatAsyncTask
    @SdkAsyncTask
    AsyncTaskPoolMonitor

    View Slide

  23. 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

    View Slide

  24. private final ThreadPoolExecutor pool;
    AsyncTaskPoolMonitor

    View Slide

  25. private final ThreadPoolExecutor pool;
    MODERN_ASYNC_TASK_CLASS_NAME = “android.support.v4.content.ModernAsyncTask”
    MODERN_ASYNC_TASK_FIELD_NAME = "THREAD_POOL_EXECUTOR";
    AsyncTaskPoolMonitor
    ThreadPoolExtractor

    View Slide

  26. BaseLayerModule ActivityLifeCycleMonitor
    Context
    Looper
    AsyncTaskPoolMonitor
    Executor
    ActivityRootLister
    EventInjector
    FailureHandler
    UiControllerModule
    Recycler
    BaseLayerComponent
    UiController

    View Slide

  27. EventInjector

    View Slide

  28. EventInjector
    EventInjectionStrategy

    View Slide

  29. EventInjector
    EventInjectionStrategy
    InputManager
    injectInputEventMethod.invoke(instanceInputManagerObject,motionEvent,motionEventMode);

    View Slide

  30. BaseLayerModule ActivityLifeCycleMonitor
    Context
    Looper
    AsyncTaskPoolMonitor
    Executor
    ActivityRootLister
    EventInjector
    FailureHandler
    UiControllerModule
    Recycler
    BaseLayerComponent
    UiController

    View Slide

  31. UiController

    View Slide

  32. UiController
    boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException;
    boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException;
    boolean injectString(String str) throws InjectEventSecurityException;

    View Slide

  33. UiController
    boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException;
    boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException;
    boolean injectString(String str) throws InjectEventSecurityException;
    EventInjector AsyncTaskMonitor IdlingResourceRegistry

    View Slide

  34. UiController
    boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException;
    boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException;
    boolean injectString(String str) throws InjectEventSecurityException;
    EventInjector AsyncTaskMonitor IdlingResourceRegistry
    boolean allResourcesAreIdle()

    View Slide

  35. ViewInteractionComponent

    View Slide

  36. ViewInteractionModule Matcher
    ViewFinder
    Matcher
    View
    ViewInteractionComponent

    View Slide

  37. ViewInteractionModule Matcher
    ViewFinder
    Matcher
    View
    ViewInteractionComponent
    throw new AmbiguousViewMatcherException
    throw new NoMatchingViewException

    View Slide

  38. ViewInteractionModule Matcher
    ViewFinder
    Matcher
    View
    ViewInteractionComponent
    public static ViewInteraction onView(final Matcher viewMatcher) {
    return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction();
    }

    View Slide

  39. ViewInteraction

    View Slide

  40. ViewInteraction
    public ViewInteraction perform(final ViewAction... viewActions)
    public ViewInteraction check(final ViewAssertion viewAssert)

    View Slide

  41. Synchronization

    View Slide


  42. perform(click());


    MainActivityTest


    View Slide


  43. perform(click());

    new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER));

    MainActivityTest


    ViewActions


    View Slide


  44. perform(click());

    new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER));

    MotionEvents.sendDown(uiController, coordinates, precision);


    MainActivityTest


    ViewActions


    Tapper


    View Slide


  45. perform(click());

    new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER));

    MotionEvents.sendDown(uiController, coordinates, precision);


    uiController.injectMotionEvent(motionEvent);


    MainActivityTest


    ViewActions


    Tapper


    MotionEvents


    View Slide

  46. InjectKeyEvent

    View Slide

  47. InjectKeyEvent
    loopMainThreadUntilIdle loopUntil
    Asynctask
    Idling

    View Slide

  48. InjectKeyEvent
    loopMainThreadUntilIdle loopUntil
    Asynctask
    Idling
    keyEventExecutor
    submit(event)
    loopUntil KeyInject

    View Slide

  49. @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());
    }

    View Slide

  50. @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());
    }

    View Slide

  51. View Slide

  52. View Slide

  53. View Slide

  54. View Slide

  55. View Slide

  56. abstract class WrappingES(delegate: ExecutorService) : ExecutorService {
    val delegate: ExecutorService = checkNotNull(delegate)
    abstract fun wrapTask(callable: Callable): Callable
    fun wrapTask(command: Runnable): Runnable {
    val wrapped = wrapTask(Executors.callable(command, null))
    return Runnable {
    try {
    wrapped.call()
    } catch (e: Exception) {
    throw RuntimeException(e)
    }
    }
    }

    View Slide

  57. abstract class WrappingES(delegate: ExecutorService) : ExecutorService {
    val delegate: ExecutorService = checkNotNull(delegate)
    abstract fun wrapTask(callable: Callable): Callable
    fun wrapTask(command: Runnable): Runnable {
    val wrapped = wrapTask(Executors.callable(command, null))
    return Runnable {
    try {
    wrapped.call()
    } catch (e: Exception) {
    throw RuntimeException(e)
    }
    }
    }

    View Slide

  58. abstract class WrappingES(delegate: ExecutorService) : ExecutorService {
    val delegate: ExecutorService = checkNotNull(delegate)
    abstract fun wrapTask(callable: Callable): Callable
    fun wrapTask(command: Runnable): Runnable {
    val wrapped = wrapTask(Executors.callable(command, null))
    return Runnable {
    try {
    wrapped.call()
    } catch (e: Exception) {
    throw RuntimeException(e)
    }
    }
    }

    View Slide

  59. override fun execute(command: Runnable) {
    delegate.execute(wrapTask(command))
    }
    override fun submit(task: Callable): Future {
    return delegate.submit(wrapTask(checkNotNull(task)))
    }
    override fun submit(task: Runnable): Future<*> {
    return delegate.submit(wrapTask(task))
    }
    override fun submit(task: Runnable, result: T): Future {
    return delegate.submit(wrapTask(task), result)
    }

    View Slide

  60. class IdlingResourceExecutorService(delegate: ExecutorService,
    val mIdlingResource: CountingIdlingResource)
    : GuavaWrappingExecutorService(delegate) {
    override fun wrapTask(callable: Callable): Callable {
    return WrappedCallable(callable)
    }
    private inner class WrappedCallable(private val delegate: Callable) : Callable
    {
    @Throws(Exception::class)
    override fun call(): T {
    mIdlingResource.increment()
    val call: T
    try {
    call = delegate.call()
    } finally {
    mIdlingResource.decrement()
    }
    return call
    }
    }
    }

    }

    View Slide

  61. class IdlingResourceExecutorService(delegate: ExecutorService,
    val mIdlingResource: CountingIdlingResource)
    : GuavaWrappingExecutorService(delegate) {
    override fun wrapTask(callable: Callable): Callable {
    return WrappedCallable(callable)
    }
    private inner class WrappedCallable(private val delegate: Callable) : Callable {
    @Throws(Exception::class)
    override fun call(): T {
    mIdlingResource.increment()
    val call: T
    try {
    call = delegate.call()
    } finally {
    mIdlingResource.decrement()
    }
    return call
    }
    }
    }

    }

    View Slide

  62. class IdlingResourceExecutorService(delegate: ExecutorService,
    val mIdlingResource: CountingIdlingResource)
    : GuavaWrappingExecutorService(delegate) {
    override fun wrapTask(callable: Callable): Callable {
    return WrappedCallable(callable)
    }
    inner class WrappedCallable(val delegate: Callable) : Callable {
    @Throws(Exception::class)
    override fun call(): T {
    mIdlingResource.increment()
    val call: T
    try {
    call = delegate.call()
    } finally {
    mIdlingResource.decrement()
    }
    return call
    }
    }
    }

    }

    View Slide

  63. View Slide

  64. 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();

    }

    };

    }


    View Slide

  65. @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();

    }

    }

    });

    }

    View Slide

  66. RxJavaPlugins.getInstance().registerSchedulersHook(RxSchedulerHook.get());


    View Slide

  67. RxIdler

    View Slide

  68. RxIdler
    https://github.com/square/RxIdler

    View Slide

  69. Rules

    View Slide

  70. @Rule
    public ActivityTestRule activityTestRule = new ActivityTestRule<>(Main){

    @Override

    protected void beforeActivityLaunched() {

    super.beforeActivityLaunched();

    }


    @Override

    protected void afterActivityLaunched() {

    super.afterActivityLaunched();

    }


    @Override

    protected void afterActivityFinished() {

    super.afterActivityFinished();

    }

    };

    View Slide

  71. @Rule
    public ActivityTestRule activityTestRule = new ActivityTestRule<>(Main){

    @Override

    protected void beforeActivityLaunched() {

    super.beforeActivityLaunched();

    }


    @Override

    protected void afterActivityLaunched() {

    super.afterActivityLaunched();

    }


    @Override

    protected void afterActivityFinished() {

    super.afterActivityFinished();

    }

    };

    View Slide

  72. @Rule
    public IntentTestRule activityTestRule = new IntentTestRule<>(Main)

    View Slide

  73. @Rule
    public IntentTestRule activityTestRule = new IntentTestRule<>(Main)


    public IntentsTestRule(Class 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();

    }



    View Slide

  74. public interface TestRule {

    Statement apply(Statement base, Description description);

    }

    View Slide

  75. 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;

    }
    }

    View Slide

  76. 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();

    }

    }

    }

    View Slide

  77. 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();

    }

    }

    };

    }

    }

    View Slide

  78. 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());


    View Slide

  79. public abstract class TestWatcher implements TestRule

    View Slide

  80. return new Statement() {

    @Override

    public void evaluate() throws Throwable {

    List errors = new ArrayList();


    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

    View Slide

  81. return new Statement() {

    @Override

    public void evaluate() throws Throwable {

    List errors = new ArrayList();


    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

    View Slide

  82. 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);

    }


    View Slide

  83. 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);

    }


    View Slide

  84. 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);

    }


    View Slide

  85. public class RuleChain implements TestRule


    View Slide

  86. 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”))));

    View Slide

  87. 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

    View Slide

  88. public NewActivityTestRule activityRule = new NewActivityTestRule<>(Main.class);


    View Slide

  89. public NewActivityTestRule activityRule = new NewActivityTestRule<>(Main.class);



    @Rule

    public RuleChain chain = RuleChain.outerRule(new FailedTest()
    .around(activityRule);


    View Slide

  90. public NewActivityTestRule 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);


    View Slide

  91. View Slide

  92. What’s next?

    View Slide

  93. Android Test Orchestrator

    View Slide

  94. Test Apk
    APK
    AndroidJunitRunner
    Android Test Orchestrator

    View Slide

  95. Test Apk
    APK
    Orchestrator
    AndroidJunitRunner
    Android Test Orchestrator

    View Slide

  96. Test Apk
    APK
    Orchestrator
    AndroidJunitRunner
    Test
    Test
    Test
    Android Test Orchestrator

    View Slide

  97. Multiprocess Espresso

    View Slide

  98. Espresso
    AndroidJunitRunner
    Multiprocess Espresso

    View Slide

  99. Espresso
    AndroidJunitRunner
    Multiprocess Espresso
    Espresso
    AndroidJunitRunner

    View Slide

  100. Espresso
    AndroidJunitRunner
    Multiprocess Espresso
    Espresso
    AndroidJunitRunner

    View Slide

  101. dependencies {
    androidTestUtil 'com.linkedin.testbutler:test-butler-app:[email protected]'
    }

    View Slide

  102. View Slide

  103. View Slide

  104. @inyaki_mwc
    Thanks

    View Slide