$30 off During Our Annual Pro Sale. View Details »

Android UI testing for beginners

Android UI testing for beginners

Introduction to Android UI testing.

Presentation Details:
------------------------------------------------------
Mobile apps are growing. Apps become more complex and require more testing. It means that it’s time to integrate automating tests to your project. What do you have for mobile applications?

This talk would be an intro to some of the feature of Espresso and UI Automator frameworks.
- General Android project problems
- General Android testing problem
- Overview of UI Automator Viewer
- Espresso and UI Automator allow you test UI part of your application and how your app interacts with Android OS
- Custom Espresso matchers

Source code:
https://github.com/AlexZhukovich/SimpleCoffeeOrderTestProject

Blog:
http://alexzh.com

Alex Zhukovich

June 30, 2016
Tweet

More Decks by Alex Zhukovich

Other Decks in Programming

Transcript

  1. Observations Ø Android has no clear way to structure of

    an application Ø Huge activities / fragments Ø Hard to add features Ø Hard to test Ø Many apps tested in manual way
  2. App

  3. Types of testing Unit testing –JUnit –Mockito –PowerMockito –etc UI

    testing –Espresso –Robotium –Robolectric –UI Automator –etc
  4. Category of Android tests Run on the JVM (local tests)

    Require Android device (instrumentation tests) Tests
  5. Running local tests in Android Studio Gradle ./gradlew test Result

    of tests: {MODULE}/build/reports/tests/ Run menu Run / Edit Configurations… Project tree
  6. Running instrumentation tests in Android Studio Gradle ./gradlew connectedAndroidTest ./gradlew

    cAT Result of tests: {MODULE}/build/reports/ androidTests/connected/ Run menu Run / Edit Configurations… Project tree
  7. UI Automator Viewer Ø Android Studio Tools / Android /

    Android Device Monitor Ø Application {ANDROID_SDK}/ tools / uiautomatorviewer
  8. What is Espresso? Ø Android UI test framework Ø Compatible

    with Android 2.2 (API level 8) or higher Ø Part of Android Testing Support Library
  9. Integrate Espresso to the project android { defaultConfig { testInstrumentationRunner

    "android.support.test.runner.AndroidJUnitRunner" } } ... dependencies { ... androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' androidTestCompile 'com.android.support.test.runner:0.5' }
  10. Recommendation for setting up the device / emulator One small

    recommendation for avoiding flakiness is turning off animation on real or virtual devices. • Window animation scale • Transition animation scale • Animator duration scale • Settings / Developer Options/
  11. First test case @RunWith(AndroidJUnit4.class) public class MainActivityEspressoTest { @Rule public

    ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class); @Test public void validateNumberPicker() { ... onView(withId(R.id.coffee_increment)) .perform(click()); onView(withId(R.id.coffee_count)) .check(matches(withText("1"))); ... } }
  12. isRoot matcher public static Matcher<Root> isRoot() { return new TypeSafeMatcher<Root>()

    { @Override public void describeTo(Description description) { description.appendText("is toast"); } @Override protected boolean matchesSafely(Root root) { if ((root.getWindowLayoutParams().get().type == WindowManager.LayoutParams.TYPE_TOAST)) { IBinder windowToken = root.getDecorView().getWindowToken(); IBinder appToken = root.getDecorView().getApplicationWindowToken(); if (windowToken == appToken) { return true; } } return false; } }; }
  13. Enter text @Test public void shouldVerifyOrderAndEnterUsername() { ... onView(withId(R.id.comment_toolbar)) .perform(typeText("Alex"),

    closeSoftKeyboard()); onView(withId(R.id.comment_toolbar)) .check(matches(withText("Alex"))); }
  14. AdapterView java.lang.Object ↳ android.view.View ↳ android.view.ViewGroup ↳ android.widget.AdapterView <T extends

    android.widget.Adapter> https://developer.android.com/reference/android/widget/AdapterView.html
  15. onData – custom adapter @Test public void clickCoffeeItem() { ...

    onData(withCoffee(espresso)) .inAdapterView(withId(R.id.detail_list)) .perform(click()); } public class CoffeeAdapter extends BaseAdapter { ... @Override public Object getItem(int position) { return mOrderedCoffee.keySet().toArray()[position]; } }
  16. withCoffee matcher public static Matcher<Object> withCoffee(final Coffee coffee) { return

    new BoundedMatcher<Object, Coffee>(Coffee.class) { @Override public void describeTo(Description description) { description.appendText("has value: "); description.appendText(coffee.toString()); } @Override protected boolean matchesSafely(Coffee item) { return item.equals(coffee); } }; }
  17. RecyclerView java.lang.Object ↳ android.view.View ↳ android.view.ViewGroup ↳ android.support.v7.widget.RecyclerView It is

    not an AdapterView https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
  18. RecyclerView dependencies { ... androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2') { // Necessary to avoid

    version conflicts exclude group: 'com.android.support', module: 'appcompat' exclude group: 'com.android.support', module: 'support-v4' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'recyclerview-v7' } } onView(withId(recyclerViewId)) .perform(RecyclerViewActions.actionOnItemAtPosition(3, click()));
  19. RecyclerViewActions Ø actionOnHolderItem Ø actionOnItem Ø actionOnItemAtPosition Ø scrollTo Ø

    scrollToHolder Ø scrollToPosition https://developer.android.com/reference/android/support/test/espresso/contrib/RecyclerViewActions.html
  20. Configure custom intents @Rule public ActivityTestRule<PaymentActivity> mActivityRule = new ActivityTestRule<PaymentActivity>(PaymentActivity.class)

    { @Override protected Intent getActivityIntent() { Intent result = new Intent(...); result.putExtra(...); return result; } };
  21. Configure custom intents @Rule public ActivityTestRule<PaymentActivity> mActivityRule = new ActivityTestRule<>(

    PaymentActivity.class, true, false); // is activity launch? @Test public void shouldTestSomething() { Context context = InstrumentationRegistry.getTargetContext(); Intent intent = new Intent(context, MainActivity.class); intent.putExtra(PaymentActivity.TOTAL_PRICE, 5.0f); mActivityRule.launchActivity(intent); ... }
  22. IdlingResource public class ServiceIdlingResource implements IdlingResource { public ServiceIdlingResource(Context context)

    { this.mContext = context; } @Override public String getName() { return ServiceIdlingResource.class.getSimpleName(); } @Override public boolean isIdleNow() { boolean idle = !isIntentServiceRunning(); if (idle && mCallback != null) { mCallback.onTransitionToIdle(); } return idle; } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { this.mCallback = callback; } private boolean isIntentServiceRunning() { ... } } http://blog.sqisland.com/2015/04/espresso-custom-idling-resource.html
  23. Using IdlingResource @Before public void setup() { mServiceIdlingResource = new

    ServiceIdlingResource(InstrumentationRegistry.getTargetContext()); registerIdlingResources(mServiceIdlingResource); } @Test public void shouldOrderCoffee() { ... } @After public void tearDown() { unregisterIdlingResources(mServiceIdlingResource); }
  24. Basic application test @Test public void shouldCheckIntentValues() { ... mActivityRule.launchActivity(intent);

    onView(withId(R.id.anim_toolbar)) .check(matches( withToolbarTitle(is(espresso.getName())))); onView(withId(R.id.recyclerView)) .check(matches(atPosition(0, context.getString(R.string.ingredients), ingredients.toString()))); ... }
  25. withToolbarTitle matcher public static Matcher<Object> withToolbarTitle(final Matcher<String> textMatcher) { return

    new BoundedMatcher<Object, Toolbar>(Toolbar.class) { @Override public boolean matchesSafely(Toolbar toolbar) { return textMatcher.matches(toolbar.getTitle()); } @Override public void describeTo(Description description) { description.appendText("with toolbar title: "); textMatcher.describeTo(description); } }; }
  26. What is UI Automator? Ø Android UI test framework Ø

    Part of Android Testing Support Library Ø Interacting with any applications Ø Interacting with a system
  27. Why is UI Automator? Ø Easy to setup Ø Readable

    tests Ø Access to device state Ø UI Automator requires Android 4.3 (API 18) or higher
  28. Integrate UI Automator to the project android { defaultConfig {

    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } } ... dependencies { ... androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1' }
  29. Integrate UI Automator to the project (API < 18) Manifest

    <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.alexzh.simplecoffeeorder"> <uses-sdk android:minSdkVersion="18" android:targetSdkVersion="23" tools:overrideLibrary="android.support.test.uiautomator.v18"/> </manifest> Test annotation @SdkSuppress(minSdkVersion = 18) public class OrderCoffeeActivity { @Test public void shouldOrderThreeEspresso() { ... } }
  30. Basic UI Automator components Ø UiDevice Ø UiCollection Ø UiObject

    / UiObject2 Ø UiScrollable Ø UiSelector Ø Configurator
  31. UiDevice Press "Home" button mUiDevice.pressHome(); Press "Back" button mUiDevice.pressBack(); Press

    at power button mUiDevice.wakeUp(); Click at arbitrary coordinates mUiDevice.click(300, 215); Open the notifications mUiDevice.openNotification();
  32. Test for calculator application @Test public void shouldSumTwoNumbers() throws UiObjectNotFoundException

    { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); mDevice.pressHome(); UiObject2 allApps = mDevice.findObject(By.desc("Apps")); allApps.click(); UiObject2 calculatorApp = mDevice.findObject(By.desc("Calculator")); calculatorApp.click(); mDevice.wait(Until.hasObject(By.text("5")), 3000); UiObject2 two = mDevice.findObject(By.text("2")); UiObject2 three = mDevice.findObject(By.text("3")); ... two.click(); plus.click(); three.click(); equal.click(); UiObject2 result = mDevice.findObject(By.res("com.android.calculator2", "formula")); assertEquals("5", result.getText()); }
  33. Example @RunWith(AndroidJUnit4.class) public class FullOrderTest { ... @Test public void

    shouldOrderThreeEspressos() { ... onView(withId(R.id.pay)).perform(click()); mDevice.openNotification(); mDevice.wait(Until.hasObject(By.text(notificationTitle)), 3000); UiObject2 title = mDevice.findObject(By.text(notificationTitle)); assertEquals(notificationTitle, title.getText()); ... } }
  34. Links • Google samples – Android architecture https://github.com/googlesamples/android-architecture • Espresso

    https://google.github.io/android-testing-support-library/docs/espresso/index.html • Testing UI for Multiple Apps https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html • SimpleCoffeeOrderTestProject https://github.com/AlexZhukovich/SimpleCoffeeOrderTestProject • Introduction to Android testing http://alexzh.com/tag/testing/ • Android Testing Codelab https://codelabs.developers.google.com/codelabs/android-testing/ @Alex_Zhukovich