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 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 Slide

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

    View Slide

  4. App

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. 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 Slide

  10. UI Automator
    Viewer

    View Slide

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

    View Slide

  12. Espresso

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. 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 Slide

  17. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  24. 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 Slide

  25. 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 Slide

  26. 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 Slide

  27. 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 Slide

  28. 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 Slide

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

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

    View Slide

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

    View Slide

  31. 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 Slide

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

  33. 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 Slide

  34. 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 Slide

  35. 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 Slide

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

    View Slide

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

    View Slide

  38. 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 Slide

  39. 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 Slide

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

    View Slide

  41. 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 Slide

  42. 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 Slide

  43. UI Automator

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  47. 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 Slide

  48. 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 Slide

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

    View Slide

  50. 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 Slide

  51. 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 Slide

  52. Espresso
    +
    UI Automator

    View Slide

  53. 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 Slide

  54. Tips

    View Slide

  55. Do not copy your code
    between tests

    View Slide

  56. Use available stuff

    View Slide

  57. Use Hamcrest library

    View Slide

  58. Focus on app behavior

    View Slide

  59. Many small tests better
    than big one

    View Slide

  60. Launch directly desired
    screen

    View Slide

  61. 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 Slide

  62. Any Questions?
    @Alex_Zhukovich

    View Slide