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

12. Testing Robot Pattern with Espresso and Kotlin

Denys Zelenchuk1 
(1)
Zürich, Switzerland
 

The next test automation pattern we discuss is the Testing Robot Pattern, which in fact is not much different than the Screen Object Pattern. The main idea behind it is similar—you separate the test implementation from the business logic. This pattern was created by Jake Wharton and was first presented in May, 2016.

Separating the What from the How

The concept of the Testing Robot Pattern is to separate what we are testing (the high-level representation of real-user application interactions or flows) from how we perform the testing (the low-level implementation of interactions performed by automated tests). Figure 12-1 shows examples of the what and how.
../images/469090_1_En_12_Chapter/469090_1_En_12_Fig1_HTML.jpg
Figure 12-1

Separating the what from the how when testing

The idea is to create a robot with as many “what” methods as there is screen functionality represented by the robot. Let’s start with basic samples and move to the final Espresso Testing Robots implementation. First, we look at the Builder Pattern (where each class method returns the same class instance), which is very similar to the Screen Object Pattern we discussed in Chapter 11. It’s the initial step on the way to the Testing Robot Pattern. Here is the BuilderToDoListRobot.kt class , which represents the Builder Pattern applied to the TO-DO list screen.

chapter12 .robots.BuilderToDoListRobot.kt Represents the Builder Pattern.
/**
 * Builder Pattern applied to TO-DO list screen.
 */
class BuilderToDoListRobot {
    fun addToDo() {
        onView(withId(R.id.fab_add_task)).perform(click())
    }
    fun showCompleted(): BuilderToDoListRobot {
        onView(withId(R.id.menu_filter)).perform(click())
        onView(allOf(withId(R.id.title), withText("Completed"))).perform(click())
        return this
    }
    fun showActive(): BuilderToDoListRobot {
        onView(withId(R.id.menu_filter)).perform(click())
        onView(allOf(withId(R.id.title), withText("Active"))).perform(click())
        return this
    }
    fun verifyToDoShown(withTitle: String): BuilderToDoListRobot {
        onView(withText(withTitle)).check(matches(isDisplayed()))
        return this
    }
    fun verifyToDoNotShown(withTitle: String): BuilderToDoListRobot {
        onView(withText(withTitle)).check(matches(not(isDisplayed())))
        return this
    }
    fun markCompleted(toDoTitle: String): BuilderToDoListRobot {
        onView(allOf(withId(R.id.todo_complete), hasSibling(withText(toDoTitle)))).perform(click())
        return this
    }
    fun checkDefaultLayout(): BuilderToDoListRobot {
        onView(withId(R.id.noTasksMain)).check(matches(isDisplayed()))
        onView(withId(R.id.noTasksIcon)).check(matches(isDisplayed()))
        return this
    }
}

In a similar way, the BuilderAddEditToDoRobot.kt class is implemented. The test case that uses the Builder Pattern is shown next.

chapter12 . RobotsTest.robotChecksToDoStateChangeBuilder() Showcases the Builder Pattern.
@Test
fun robotChecksToDoStateChangeBuilder() {
    BuilderToDoListRobot()
            .checkDefaultLayout()
            .addToDo()
    BuilderAddEditToDoRobot()
            .title(toDoTitle)
            .description(toDoDescription)
            .done()
    BuilderToDoListRobot()
            .verifyToDoShown(toDoTitle)
            .markCompleted(toDoTitle)
            .showActive()
            .verifyToDoNotShown(toDoTitle)
            .showCompleted()
            .verifyToDoShown(toDoTitle)
}

You can see that the BuilderToDoListRobot.kt test class functions represent the “how” and the test case steps show us the “what”. Notice the clear screen separation—each time we start the test or go to a different screen (i.e., an Activity or Fragment), we create a new class instance. This approach works, but it is far from the final Robot Pattern implementation.

The next step is to use the Kotlin language advantages to simplify the Builder Pattern implementation. To understand how this is done, we must refer to the chapter12.ToDoListRobot.kt file, where the ToDoListRobot class together with toDoList() function are declared.

Declared in chapter12 .robots.ToDoListRobot.kt.
/**
 * Extension function that takes ToDoListRobot class function(s)
 * as a parameter, executes this function(s), and returns a
* ToDoListRobot instance.
 */
fun toDoList(func: ToDoListRobot.() -> Unit) = ToDoListRobot().apply { func() }
  1. 1.

    Here, toDoList(func: ToDoListRobot.() -> Unit) is an extension function that accepts the ToDoListRobot function func as a parameter. Based on the Unit type, you can guess that the func function returns nothing.

     
  2. 2.
    The apply { func() } function in the ToDoListRobot().apply { func() } expression executes the provided functions inside the apply() function block as if they are called from inside the ToDoListRobot class. This is possible due to the nature of the apply() function, which according to Kotlin documentation:
    /**
     * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
     */

    Where this in the current case is the ToDoListRobot class.

     
In this same way, we implement a similar function for the AddEditToDoRobot class :
fun addEditToDo(func: AddEditToDoRobot.() -> Unit) = AddEditToDoRobot().apply { func() }

Now let’s see how two static functions transform the previously discussed test case.

Test Case Where Robot Constructors Are Replaced by Extension Functions. chapter12 . RobotsTest.robotChecksToDoStateChangeRobotsSeparation().
@Test
fun robotChecksToDoStateChangeRobotsSeparation() {
    toDoList {
        checkDefaultLayout()
        addToDo()
    }
    addEditToDo {
        title(toDoTitle)
        description(toDoDescription)
        done()
    }
    toDoList {
        verifyToDoShown(toDoTitle)
        markCompleted(toDoTitle)
        showActive()
        verifyToDoNotShown(toDoTitle)
        showCompleted()
        verifyToDoShown(toDoTitle)
    }
}

As you can see, we no longer need the constructors. Instead, we call the static functions toDoList{ } and addEditTodo{ }, which act on behalf of the ToDoListRobot and AddEditToDoRobot classes .

In the next step, the functions that return new robots are modified. In the test case we work with, it is the addToDo() and done() functions . So, it becomes almost equivalent to the toDoList{ } and addEditTodo{ } static functions:
infix fun addToDo(func: AddEditToDoRobot.() -> Unit): AddEditToDoRobot {
    onView(withId(R.id.fab_add_task)).perform(click())
    return AddEditToDoRobot().apply(func)
}

The test case is changed into the following.

Test Case Where Robots Transition Functions Act Similarly to toDoList{ } and addEditTodo{ }. chapter12 .RobotsTest.robotChecksToDoStateChange().
@Test
fun robotChecksToDoStateChange() {
    toDoList {
        checkDefaultLayout()
    }.addToDo {
        title(toDoTitle)
        description(toDoDescription)
    }.done {
        verifyToDoShown(toDoTitle)
        markCompleted(toDoTitle)
        showActive()
        verifyToDoNotShown(toDoTitle)
        showCompleted()
        verifyToDoShown(toDoTitle)
    }
}
The last thing we have to do to make the Testing Robot Pattern better is to use Kotlin’s infix function notation for functions that return new robots. The infix notation allows us to call a function without using the period and brackets:
infix fun addTask(func: AddEditToDoRobot.() -> Unit): AddEditToDoRobot {
    onView(withId(R.id.fab_add_task)).perform(click())
    return AddEditToDoRobot().apply(func)
}

This is how the final test case looks.

Test Case Where Robots Transition Functions Act Similarly to toDoList{ } and addEditTodo{ } . chapter12 .RobotsTest.robotChecksToDoStateChange().
@Test
fun robotChecksToDoStateChangeInfix() {
    toDoList {
        checkDefaultLayout()
    } addToDo {
        title(toDoTitle)
        description(toDoDescription)
    } done {
        verifyToDoShown(toDoTitle)
        markCompleted(toDoTitle)
        showActive()
        verifyToDoNotShown(toDoTitle)
        showCompleted()
        verifyToDoShown(toDoTitle)
    }
}

At this point, we have a clear separation of what we are testing from the how we do the test. The test case in this chapter represents the business logic of what should be tested. This keeps the test structure short, logical, and without technical implementation details.

To improve it even more, we can use Kotlin’s inner classes to represent smaller view groups, like filtering or menus belonging to the same robot. Take a look at the example shown in Figure 12-2.

As shown in Figure 12-2, the TO-DO list screen is split into three parts:
  1. 1.

    The main functional area is represented by the ToDoListRobotWithInnerClasses class.

     
  2. 2.

    A filter view group called ToDoListFilter is declared as an inner class.

     
  3. 3.

    A menu view group called ToDoListMenu is declared as an inner class.

     
../images/469090_1_En_12_Chapter/469090_1_En_12_Fig2_HTML.jpg
Figure 12-2

Adding inner classes inside a robot class

For convenience, we added Espresso actions that trigger view groups to appear inside inner classes constructors.

Inner Class Inside the TasksListRobotWithInnerClasses Class .
fun toDoListFilter(func: ToDoListFilter.() -> Unit) = ToDoListFilter().apply { func() }
inner class ToDoListFilter {
    init {
        onView(withId(R.id.menu_filter)).perform(click())
    }
    fun showAll() {
        onView(allOf(withId(R.id.title), withText("All"))).perform(click())
    }
    fun showCompleted() {
        onView(allOf(withId(R.id.title), withText("Completed"))).perform(click())
    }
    fun showActive() {
        onView(allOf(withId(R.id.title), withText("Active"))).perform(click())
    }
}
Using this inner classes approach, we actually gain more benefits:
  • Code readability

  • Code duplication elimination

Code Readability

The robot class implementation and the test code both become more readable and easier to maintain due to the clear split into functional areas or view groups.

Code Duplication Elimination

Inner class constructors can execute the steps needed to trigger functionality, such as clicking the Filter toolbar icon to show all possible filtering options and then navigate through the options.

Exercise 27

Writing Tests Using the Screen Testing Robots Pattern
  1. 1.

    Create robots for the Settings and Statistics screen.

     
  2. 2.

    Think about the menu drawer and implement a solution that will best fit its functionality. Write a test that involves the menu drawer navigation.

     
..................Content has been hidden....................

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