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

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. Android UI testing
    for beginners
    @Alex_Zhukovich
    http://alexzh.com

    View full-size slide

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

    View full-size slide

  3. Solution
    ü Architectural patterns (MVP, MVVM, etc)
    ü Automated testing
    ü Test coverage
    ü Continuous Integration

    View full-size slide

  4. Types of testing
    Unit testing
    –JUnit
    –Mockito
    –PowerMockito
    –etc
    UI testing
    –Espresso
    –Robotium
    –Robolectric
    –UI Automator
    –etc

    View full-size slide

  5. Android project structure
    Source code
    module/src/main/java
    Unit tests
    module/src/test/java
    Instrumentation tests
    module/src/androidTest/java

    View full-size slide

  6. Category of Android tests
    Run on the JVM
    (local tests)
    Require Android
    device
    (instrumentation tests)
    Tests

    View full-size slide

  7. Running local tests in Android Studio
    Gradle
    ./gradlew test
    Result of tests:
    {MODULE}/build/reports/tests/
    Run menu
    Run / Edit Configurations…
    Project tree

    View full-size slide

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

    View full-size slide

  9. UI Automator
    Viewer

    View full-size slide

  10. UI Automator Viewer
    Ø Android Studio
    Tools / Android / Android Device
    Monitor
    Ø Application
    {ANDROID_SDK}/ tools /
    uiautomatorviewer

    View full-size slide

  11. What is Espresso?
    Ø Android UI test framework
    Ø Compatible with Android 2.2 (API level 8) or higher
    Ø Part of Android Testing Support Library

    View full-size slide

  12. Why is Espresso?
    Ø Easy to setup
    Ø Readable tests
    Ø Extendable
    Ø Smart waiting

    View full-size slide

  13. Install / Update Android Support Libraries
    Ø Android SDK Manager
    Ø Terminal

    View full-size slide

  14. 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'
    }

    View full-size slide

  15. 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/

    View full-size slide

  16. Espresso UI tests
    onView(ViewMatcher)
    .perform(ViewAction)
    .check(ViewAssertion);
    onData(ObjectMatcher)
    .DataOptions
    .perform(ViewAction)
    .check(ViewAssertion);

    View full-size slide

  17. ViewMatcher
    onView(withId(R.id.pay_button)) ← ViewMatcher
    .perform(ViewAction)
    .check(ViewAssertion);

    View full-size slide

  18. ViewAction
    onView(withId(R.id.pay_button))
    .perform(click()) ← ViewAction
    .check(ViewAssertion);

    View full-size slide

  19. ViewAssertion
    onView(withId(R.id.pay_button))
    .perform(click())
    .check(matches(isDisplayed())); ← ViewAssertion

    View full-size slide

  20. Is it pass?
    @Test
    public void testSomeView() {
    onView(withId(R.id.test));
    }

    View full-size slide

  21. Hamcrest
    http://www.marcphilipp.de/downloads/posts/2013-01-02-hamcrest-quick-reference/Hamcrest-1.3.pdf

    View full-size slide

  22. First test case
    @RunWith(AndroidJUnit4.class)
    public class MainActivityEspressoTest {
    @Rule
    public ActivityTestRule 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")));
    ...
    }
    }

    View full-size slide

  23. Snackbar
    @Test
    public void shouldDisplayOrderMoreCoffeeSnackBar() {
    onView(withId(R.id.pay))
    .perform(click());
    onView(withId(android.support.design.R.id.snackbar_text))
    .check(matches(withText(R.string.message_more_coffee)));
    }

    View full-size slide

  24. Toast
    @Test
    public void shouldDisplayOrderMoreCoffeeToast() {
    onView(withId(R.id.pay))
    .perform(click());
    onView(withText(R.string.message_more_coffee))
    .inRoot(isRoot())
    .check(matches(isDisplayed()));
    }

    View full-size slide

  25. isRoot matcher
    public static Matcher isRoot() {
    return new TypeSafeMatcher() {
    @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;
    }
    };
    }

    View full-size slide

  26. 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")));
    }

    View full-size slide

  27. AdapterView
    java.lang.Object
    ↳ android.view.View
    ↳ android.view.ViewGroup
    ↳ android.widget.AdapterView

    https://developer.android.com/reference/android/widget/AdapterView.html

    View full-size slide

  28. onData
    onData(ObjectMatcher)
    .DataOptions
    .perform(ViewAction)
    .check(ViewAssertion)

    View full-size slide

  29. onData – ArrayAdapter
    onData(allOf(is(instanceOf(String.class)), is("Americano")))
    .check(matches(isDisplayed()));
    ArrayAdapter adapter = new ArrayAdapter<>(
    this,
    android.R.layout.simple_list_item_1,
    android.R.id.text1,
    coffeeList);

    View full-size slide

  30. 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];
    }
    }

    View full-size slide

  31. withCoffee matcher
    public static Matcher withCoffee(final Coffee coffee) {
    return new BoundedMatcher(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);
    }
    };
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. RecyclerViewActions
    Ø actionOnHolderItem
    Ø actionOnItem
    Ø actionOnItemAtPosition
    Ø scrollTo
    Ø scrollToHolder
    Ø scrollToPosition
    https://developer.android.com/reference/android/support/test/espresso/contrib/RecyclerViewActions.html

    View full-size slide

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

    View full-size slide

  36. Configure custom intents
    @Rule
    public ActivityTestRule 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);
    ...
    }

    View full-size slide

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

    View full-size slide

  38. Using IdlingResource
    @Before
    public void setup() {
    mServiceIdlingResource =
    new ServiceIdlingResource(InstrumentationRegistry.getTargetContext());
    registerIdlingResources(mServiceIdlingResource);
    }
    @Test
    public void shouldOrderCoffee() {
    ...
    }
    @After
    public void tearDown() {
    unregisterIdlingResources(mServiceIdlingResource);
    }

    View full-size slide

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

    View full-size slide

  40. withToolbarTitle matcher
    public static Matcher withToolbarTitle(final Matcher textMatcher) {
    return new BoundedMatcher(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);
    }
    };
    }

    View full-size slide

  41. UI Automator

    View full-size slide

  42. What is UI Automator?
    Ø Android UI test framework
    Ø Part of Android Testing Support Library
    Ø Interacting with any applications
    Ø Interacting with a system

    View full-size slide

  43. Why is UI Automator?
    Ø Easy to setup
    Ø Readable tests
    Ø Access to device state
    Ø UI Automator requires Android 4.3 (API 18) or higher

    View full-size slide

  44. Install / Update Android Support Libraries
    Ø Android SDK Manager
    Ø Terminal

    View full-size slide

  45. 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'
    }

    View full-size slide

  46. Integrate UI Automator to the project (API < 18)
    Manifest

    xmlns:tools="http://schemas.android.com/tools"
    package="com.alexzh.simplecoffeeorder">
    android:minSdkVersion="18"
    android:targetSdkVersion="23"
    tools:overrideLibrary="android.support.test.uiautomator.v18"/>

    Test annotation
    @SdkSuppress(minSdkVersion = 18)
    public class OrderCoffeeActivity {
    @Test
    public void shouldOrderThreeEspresso() {
    ...
    }
    }

    View full-size slide

  47. Basic UI Automator components
    Ø UiDevice
    Ø UiCollection
    Ø UiObject / UiObject2
    Ø UiScrollable
    Ø UiSelector
    Ø Configurator

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  50. Espresso
    +
    UI Automator

    View full-size slide

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

    View full-size slide

  52. Do not copy your code
    between tests

    View full-size slide

  53. Use available stuff

    View full-size slide

  54. Use Hamcrest library

    View full-size slide

  55. Focus on app behavior

    View full-size slide

  56. Many small tests better
    than big one

    View full-size slide

  57. Launch directly desired
    screen

    View full-size slide

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

    View full-size slide

  59. Any Questions?
    @Alex_Zhukovich

    View full-size slide