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

Espresso 2.0

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Espresso 2.0

Meetup PAUG

Avatar for Thomas Guerin

Thomas Guerin

February 07, 2015
Tweet

More Decks by Thomas Guerin

Other Decks in Programming

Transcript

  1. AndroidJUnitRunner AndroidJUnitRunner @Tom404_ Contains all improvements of GoogleInstrumentationTestRunner: • Application

    onCreate has finished before testing begins • Instrumentation finished == Activities finished • Reliable Activity monitoring • New Features
  2. AndroidJUnitRunner New features @Tom404_ Test Filters Test Timeout Sharding of

    tests Activity monitoring mechanism ActivityLifecycleMonitorRegistry JUnit4 support
  3. AndroidJUnitRunner Test filters @Tom404_ @RequiresDevice @[Small | Medium | Large]Test

    @SdkSupress // Launch tests given a size adb shell am instrument -w -e size [small | medium | large] com.application.name/android.support.test.runner.AndroidJUnitRunner
  4. AndroidJUnitRunner Sharding @Tom404_ // Create 4 tests shards and run

    the second shard of tests adb shell am instrument -w -e numShards 4 -e shardIndex 1 com.application.name/android.support.test.runner.AndroidJUnitRunner
  5. AndroidJUnitRunner Activity monitoring @Tom404_ // Find activities given a stage

    (CREATED, STARTED, PAUSED, RESUMED, STOPPED, // DESTROYED) Set<Activity> pausedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.PAUSED);
  6. AndroidJUnitRunner Activity monitoring @Tom404_ // Find activities given a stage

    (CREATED, STARTED, PAUSED, RESUMED, STOPPED, // DESTROYED) Set<Activity> pausedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.PAUSED); // Get notified when activity lifecycle changes ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(callback) ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(callback)
  7. Hamcrest Hamcrest Matchers library assertThat("Hello", equalTo("Hello")) // Syntactic sugar assertThat(“Hello",

    is("Hello")) // A lot of matchers available allOf not instanceOf hasProperty equalToIgnoringCase @Tom404_
  8. Hamcrest Hamcrest Matchers library assertThat("Hello", equalTo("Hello")) // Syntactic sugar assertThat(“Hello",

    is("Hello")) Easy to compose // A lot of matchers available allOf not instanceOf hasProperty equalToIgnoringCase @Tom404_
  9. Hamcrest Hamcrest custom matcher public class IsNotANumber extends TypeSafeMatcher<Double> {

    @Override public boolean matchesSafely(Double number) { return number.isNaN(); } public void describeTo(Description description) { description.appendText("not a number"); } public static Matcher<Double> notANumber() { return new IsNotANumber(); } } @Tom404_
  10. Espresso 1.x Example onView(allOf(withId(R.id.my_button), withText(“My Button”))) .check(matches(isDisplayed())) .perform(click()); // A

    simple extract makes It easier to read onView(withIdAndText(R.id.my_button, "My Button”)) .check(matches(isDisplayed())) .perform(click()); @Tom404_
  11. Espresso 1.x Example onView(allOf(withId(R.id.my_button), withText(“My Button”))) .check(matches(isDisplayed())) .perform(click()); // A

    simple extract makes It easier to read onView(withIdAndText(R.id.my_button, "My Button”)) .check(matches(isDisplayed())) .perform(click()); // If button is not displayed, perform(click()) will fail onView(withIdAndText(R.id.my_button, "My Button")).perform(click()); @Tom404_
  12. Espresso 1.x Example onView(allOf(withId(R.id.my_button), withText(“My Button”))) .check(matches(isDisplayed())) .perform(click()); Not enough

    restrictions == AmbiguousViewMatcherException // A simple extract makes It easier to read onView(withIdAndText(R.id.my_button, "My Button”)) .check(matches(isDisplayed())) .perform(click()); // If button is not displayed, perform(click()) will fail onView(withIdAndText(R.id.my_button, "My Button")).perform(click()); @Tom404_
  13. onData(allOf(is(instanceOf(String.class)), is("textToFind"))).perform(click()); // Or directly to a position onData(is(instanceOf(String.class))).atPosition(0).perform(click()); //

    Specify a listview, useful for viewPager onData(allOf(is(instanceOf(String.class)), is("textToFind"))) .inAdapterView(withId(R.id.my_list)).perform(click()); // Interaction with child view in a row onData(allOf(is(instanceOf(String.class)), is("textToFind"))) .onChildView(withId(R.id.my_child)) .perform(click()); Espresso 1.x Listview case @Tom404_
  14. // Type returned by adapter getItem method public class Item

    { public String name; public String itemContent; } More complex case onData(allOf(is(instanceOf(Item.class)), hasProperty(“name", equalTo(“nameToFind")))) .perform(click()); Espresso 1.x Listview case @Tom404_
  15. public static Matcher<Object> withItemName(final Matcher<String> itemTextMatcher){ return new BoundedMatcher<Object, Item>(Item.class)

    { @Override public boolean matchesSafely(Item item) { return itemTextMatcher.matches(item.name); } @Override public void describeTo(Description description) { description.appendText("with item name: "); itemTextMatcher.describeTo(description); } }; } Custom matcher onData(withItemName(equalTo("nameToFind"))).perform(click()); Espresso 1.x Listview case @Tom404_
  16. com.google.android.apps.common.testing.ui.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: is

    <1234> If the target view is not part of the view hierarchy, you may need to use Espresso.onData to load it from one of the following AdapterViews:android.widget.ListView{426f6f18 VFED.VC. ......ID 0,0-720,1557} - android.widget.ListView{4269c598 V.ED.VC. ........ 48,393-1032,1509 #7f070042 app:id/my_list} View Hierarchy: +>DecorView{id=-1, visibility=VISIBLE, width=1080, height=1776, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is- layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1} | +->ActionBarOverlayLayout{id=16909075, res-name=action_bar_overlay_layout, visibility=VISIBLE, width=1080, height=1776, has-focus=true, has-focusable=true, has-window-focus=true, is- clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is- selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child- count=3} | +-->FrameLayout{id=16908290, res-name=content, visibility=VISIBLE, width=1080, height=1557, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is- focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout- requested=false, has-input-connection=false, x=0.0, y=219.0, child-count=1} // Try to to find an unknown view onView(withId(1234)); Espresso 1.x Meaningful error logs @Tom404_
  17. Under the hood Goodbye flakyness ! For each test action

    Espresso will : 1. Wait until the app is idle 2. Run on the UI thread 3. Wait until completion 4. Check the result @Tom404_
  18. Under the hood Goodbye flakyness ! onView(withId(R.id.start_activity_button)).perform(click()); onView(withId(R.id.view_in_new_activity)).perform(click()); For each

    test action Espresso will : 1. Wait until the app is idle 2. Run on the UI thread 3. Wait until completion 4. Check the result @Tom404_
  19. Under the hood Goodbye flakyness ! onView(withId(R.id.start_activity_button)).perform(click()); onView(withId(R.id.view_in_new_activity)).perform(click()); For each

    test action Espresso will : 1. Wait until the app is idle 2. Run on the UI thread 3. Wait until completion 4. Check the result @Tom404_
  20. Use IdlingResource interface for custom implementation Under the hood Idle

    but active ! Espresso synchronizes AsyncTask default thread pool @Tom404_
  21. Under the hood Idle but active ! class EspressoThreadPool extends

    ThreadPoolExecutor implements IdlingResource { private int threadCount = 0; private ResourceCallback resourceCallback; ... @Override public synchronized void execute(Runnable r) { threadCount++; super.execute(r); } @Override public synchronized boolean isIdleNow() { return threadCount == 0; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } @Override protected synchronized void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); threadCount--; if (resourceCallback != null && isIdleNow()) { resourceCallback.onTransitionToIdle(); } } } @Tom404_
  22. Under the hood Idle but active ! @Before public void

    setUp() { Espresso.registerIdlingResources(espressoThreadPool); } @Tom404_ @After public void tearDown() { Espresso.unregisterIdlingResources(espressoThreadPool); }
  23. Espresso 2.0 Other changes @Tom404_ PickerActions Switched target SDK 21

    Many new sample activities/tests RecyclerViewActions
  24. Espresso 2.0 Webview support @Tom404_ “While testing internally, we discovered

    some issues, which required major changes. We are putting the finishing touches on a new and improved API - it is coming shortly and it promises to be awesome.”
  25. Espresso 2.0 Webview support @Tom404_ “While testing internally, we discovered

    some issues, which required major changes. We are putting the finishing touches on a new and improved API - it is coming shortly and it promises to be awesome.”
  26. Tips Priority Job Queue @Tom404_ public abstract class BaseJob extends

    Job { public static final AtomicInteger COUNTER = new AtomicInteger(0); @Override public final void onAdded() { COUNTER.incrementAndGet(); } @Override public final void onRun() throws Throwable { run(); COUNTER.decrementAndGet(); } protected abstract void run() throws Throwable; @Override protected final void onCancel() { cancel(); COUNTER.decrementAndGet(); } }
  27. Tips Priority Job Queue @Tom404_ public class PriorityJobQueueIdleMonitor implements IdlingResource

    { private AtomicBoolean wasIdle = new AtomicBoolean(true); private volatile ResourceCallback resourceCallback; … @Override public boolean isIdleNow() { boolean isIdle = BaseJob.COUNTER.get() == 0; boolean wasIdle = this.wasIdle.getAndSet(isIdle); if (isIdle && !wasIdle && resourceCallback != null) { resourceCallback.onTransitionToIdle(); resourceCallback = null; } return isIdle; } }