© Denys Zelenchuk 2019
Denys ZelenchukAndroid Espresso Revealedhttps://doi.org/10.1007/978-1-4842-4315-2_15

15. Improving Productivity and Testing Unusual Components

Denys Zelenchuk1 
(1)
Zürich, Switzerland
 

This chapter contains code samples that were not covered in other chapters and Espresso testing tips that may increase your daily test writing productivity.

Creating Parameterized Tests

Sometimes we may have a need to write a single test that is applicable to many similar cases. For example, we might need a test that validates how the same EditText field behaves with different String values provided as input. In this case, the JUnit Parameterized custom runner can be used. It allows us to have one test inside a parameterized class ( https://github.com/junit-team/junit4/wiki/parameterized-tests ). The following example demonstrates a parameterized test class with a single parameter.

chapter15 . parameterizedtest.ParameterizedTestSingleParameter.kt .
/**
 * Parameterized test with single parameter.
 */
@RunWith(value = Parameterized::class)
class ParameterizedTestSingleParameter(private val title: String) : BaseTest() {
    @Test
    fun usesSingleParameters() {
        // Add new TO-DO.
        onView(withId(R.id.fab_add_task)).perform(click())
        onView(withId(R.id.add_task_title))
                .perform(typeText(title), closeSoftKeyboard())
        onView(withId(R.id.fab_edit_task_done)).perform(click())
        // Verify new TO-DO with title is shown in the TO-DO list.
        onView(withText(title)).check(matches(isDisplayed()))
    }
    companion object {
        @JvmStatic
        @Parameterized.Parameters
        fun data() = listOf(
                TodoItem().title,
                TodoItem().title,
                TodoItem().title)
    }
}

During the test run, each instance of the ParameterizedTestSingleParameter class will be constructed using the provided title argument. So, in the end, we will have as many test runs as the number of parameters provided. In this case, it is three.

A parameterized test class with multiple test parameters can be created in a similar way, as follows.

chapter15 . parameterizedtest.ParameterizedTestMultipleParameters.kt .
/**
 * Parameterized test with multiple parameters.
 */
@RunWith(value = Parameterized::class)
class ParameterizedTestMultipleParameters(
        private val title: String,
        private val description: String) : BaseTest() {
    @Test
    fun usesMultipleParameters() {
        // Add new TO-DO.
        onView(withId(R.id.fab_add_task)).perform(click())
        onView(withId(R.id.add_task_title))
                .perform(typeText(title), closeSoftKeyboard())
        onView(withId(R.id.add_task_description))
                .perform(typeText(description), closeSoftKeyboard())
        onView(withId(R.id.fab_edit_task_done)).perform(click())
        // Verify new TO-DO with title is shown in the TO-DO list.
        onView(withText(title)).check(matches(isDisplayed()))
    }
    companion object {
        @JvmStatic
        @Parameterized.Parameters
        fun data() = arrayOf(
                arrayOf("item 1", "description 1"),
                arrayOf("item 2", "description 2"),
                arrayOf("item 3", "description 3"))
    }
}

Aggregating Tests into Test Suites

In order to run a set of tests that may belong or may test for a particular application functionality, they are best organized into test suites. The JUnit Suite Runner can be the right choice here. It allows you to manually build a suite containing tests from many test classes. Here is how it looks.

chapter15 . testsuite.TestSuiteSample.kt
/**
 * Organizing test classes into a test suite.
 */
@RunWith(Suite::class)
@Suite.SuiteClasses(
        AddToDoTest::class,
        EditToDoTest::class,
        FilterToDoTest::class)
class TestSuiteSample

This way, you can organize tests into a logical structure depending on the tested functionality or on the test types, such as Smoke, Regression, etc.

Using AndroidStudio Live Templates in UI Tests

The code completion feature in many modern integrated development environments (IDEs) increased our code writing productivity quite a lot. But thanks to AndroidStudio, we have an even more powerful tool that can help us code even faster—live templates.

Live templates are predefined code snippets that can be inserted into code by typing their abbreviations. They can be added to AndroidStudio via Preferences ➤ Editor ➤ Live Templates. There are a set of predefined Live Templates groups and you can add your own group or your own live template. To do this, click the + button and select the proper option. After that, you provide an abbreviation name, template text (the code snippet that will be later inserted instead of the abbreviation), an optional description, and the proper context where your template will be available. In our case, the Kotlin and Java contexts were selected, as shown in Figure 15-1.
../images/469090_1_En_15_Chapter/469090_1_En_15_Fig1_HTML.jpg
Figure 15-1

Adding a live template

The created templates can be used in specified contexts, by just typing the template abbreviation and clicking the Tab button, as shown in Figure 15-2.
../images/469090_1_En_15_Chapter/469090_1_En_15_Fig2_HTML.jpg
Figure 15-2

Using a live template by typing the abbreviation and clicking Tab

It is also easy to export predefined live templates when you switch your computer or want to share them. To do this, just open the File ➤ Export Settings … menu and select Live Templates, as shown in Figure 15-3.
../images/469090_1_En_15_Chapter/469090_1_En_15_Fig3_HTML.jpg
Figure 15-3

Exporting live templates

For your convenience, a small list of Espresso live templates was created and exported as a .jar file into the TO-DO application project in the following path: todoapp/book_assets/livetemplates.jar.

Espresso Drawable Matchers

Another custom matcher type that we must know about are the Drawable matchers . This matcher type can be used to compare icons and images. These types of validation in UI tests are not widely discussed and, in general, Android snapshot tests are not popular and can be done only by using third-party libraries. To cover this area, we introduce additional drawables validation as part of the UI tests.

So what do we compare the application drawables to? We must remember that a test is built as a separate application and has its own resources, context, etc. So, the solution is simple—we just copy the application’s drawables that will be used in tests into a test application resource and use them in UI tests. Here is the Drawable matchers implementation that covers the TextView and ImageView drawables .

chapter15.drawablematchers.DrawableMatchers.kt.
/**
 * Contains TextView and ImageView Drawable matchers.
 */
class DrawableMatchers {
    fun withTextViewDrawable(drawableToMatch: Drawable): Matcher<View> {
        return object : BoundedMatcher<View, TextView>(TextView::class.java) {
            override fun describeTo(description: Description) {
                description.appendText("Drawable in TextView $drawableToMatch")
            }
            override fun matchesSafely(editTextField: TextView): Boolean {
                val drawables = editTextField.compoundDrawables
                val drawable = drawables[2]
                return isSameBitmap(drawableToMatch, drawable)
            }
        }
    }
    fun withImageViewDrawable(expectedDrawable: Drawable?): Matcher<View> {
        return object : BoundedMatcher<View, ImageView>(ImageView::class.java) {
            override fun describeTo(description: Description) {
                description.appendText("Drawable in ImageView $expectedDrawable")
            }
            public override fun matchesSafely(imageView: ImageView) =
                    isSameBitmap(imageView.drawable, expectedDrawable)
        }
    }
    fun isSameBitmap(drawable: Drawable?, expectedDrawable: Drawable?): Boolean {
        var localDrawable = drawable
        var localExpectedDrawable = expectedDrawable
        // Return if null.
        if (localDrawable == null || localExpectedDrawable == null) {
            return false
        }
        // StateListDrawable lets you assign a number of graphic images to a single
        // Drawable and swap out the visible item by a string ID value.
        if (localDrawable is StateListDrawable
          && localExpectedDrawable is StateListDrawable) {
                  localDrawable = localDrawable.current
                  localExpectedDrawable = localExpectedDrawable.current
        }
        // BitmapDrawable - a Drawable that wraps a bitmap and can be tiled, stretched, or
        // aligned.
        if (localDrawable is BitmapDrawable) {
            val bitmap = localDrawable.bitmap
            val otherBitmap = (localExpectedDrawable as BitmapDrawable).bitmap
            return bitmap.sameAs(otherBitmap)
        }
        return false
    }
}

Here is its usage.

chapter15.drawablematchers.DrawableMatchersTest.kt.
/**
 * Demonstrates Drawable matchers usage.
 */
class DrawableMatchersTest : BaseTest() {
    @Test
    fun checkDrawableInMenuDrawer() {
        openDrawer()
        onView(withId(R.id.headerTodoLogo))
                .check(matches(DrawableMatchers()
                        .withImageViewDrawable(getMenuIconDrawable())))
    }
    private fun getMenuIconDrawable(): Drawable? {
        val drawableId = com.example.android.architecture.blueprints.todoapp.mock.test
                .R.drawable.test_logo
        return InstrumentationRegistry.getInstrumentation().context.getDrawable(drawableId)
    }
}

In this test, we are comparing the icon shown inside the TO-DO application drawer called logo.png name in main application drawables with the one stored in the test application drawable resources called test_logo .

Note

It is not possible to import the R.class file from the main and test applications, so we have to explicitly provide the path to test the application R.class.

Setting SeekBar Progress in Espresso UI Tests

This section demonstrates how to set the SeekBar progress with a custom Espresso ViewAction . We know from Chapter 2 how to create a custom ViewAction and the SeekBar case is one of the simplest.

chapter15 .setseekbarprogress.SeekBarViewActions.kt.
/**
 * ViewActions that operate on SeekBar
 */
object SeekBarViewActions {
    /**
     * Sets progress of a SeekBar.
     *
     * @param value - the progress value between min and max SeekBar value
     */
    fun setProgress(value: Int): ViewAction {
        return object : ViewAction {
            override fun getConstraints(): Matcher<View> {
                return isAssignableFrom(SeekBar::class.java)
            }
            override fun getDescription(): String {
                return ("Set slider progress to $value.")
            }
            override fun perform(uiController: UiController, view: View) {
                val seekBar = view as SeekBar
                seekBar.progress = value
            }
        }
    }
}

The usage in the test also looks simple, as follows.

chapter15 .setseekbarprogress. SetSeekBarProgressTest.kt .
/**
 * Testing SeekBar change.
 */
class SetSeekBarProgressTest: BaseTest() {
    @Test
    fun sliderActionSample() {
        openDrawer()
        onView(allOf(withId(R.id.design_menu_item_text),
                withText(R.string.statistics_title))).perform(click())
        onView(withId(android.R.id.button1)).perform(click())
        onView(withId(R.id.simpleSeekBar)).perform(setProgress(10))
        onView(withId(R.id.seekBarTextView)).check(matches(withText("Progress: 10")))
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset