Espresso tests are pretty unwieldy out of the box. Here’s a take on better architecture through layers.

Lessons from the Browser Age

Web test automation has a best practice called “Page Object Models”, which separates test code into two parts:

  1. An abstraction layer which models the application under test
  2. Business use cases which leverage page models execute the tests.

A login test without POMs might look like this:

# login.rb
find_css(“input#username”).set(“joe9”)
find_css(“input#password”).set(“999”)
find_xpath(“//button[@title=’submit’]”).click

This sort of code doesn’t scale well. If your application changes — and of course it will, this is software we’re talking about — you have to update each test that exercises the changed code. Instead, POMs centralize our application references into one single place like so:

# login_page.rb
class LoginPage < SitePrism::Page
element :username, “input#username”
element :password, “input#password”
element :submit, :xpath, “//button[@title=’submit’]”
end

With this particular implementation we’re using the excellent SitePrism toolkit, also the namesake for our little sibling project we’ve got going here. (Thanks big bro!)

A test execution with POMs might like like so:

# login_test.rb
LoginScreen.username.set(‘joe9’)
LoginScreen.password.set(‘999’)
LoginScreen.submit.click

With POMs, when our app changes, we only need to modify the element or page that changed. Easier to maintain, cleaner, more intuitive. An established best practice in the browser world.

To the mobile, Alice

Let’s see if we can implement something similar in Java with Android Espresso. As our workbench we’ll use Omni Notes, an open source app for taking notes, making todo lists, and setting reminders. Out of the box, here’s what it looks like when we create a new note using Espresso Test Recorder:

Espresso out of the box. The documentation claims we can write “beautiful” tests. I suppose someone might consider a Chevy Cavalier beautiful … compared to a Plymouth Reliant. Smh.

From an architecture standpoint, this isn’t very great. We are combining how we find things, with what we are doing. A clear violation of Single Responsibility.

Test execution code should focus on business behavior

How about something like this instead:

Test execution code can focus on describing a use case.

With this code, we’re focused solely on describing the use case. The actual work of running the test, we can push into layers of complexity underneath, each layer with a discrete single responsibility.

Our first technical layer: Screen Models

We’ve got two top-level references in our createNewNote() test, StartScreen and NewNoteScreen. These “screen models” are responsible for collecting all the elements we want to interact with, assigning a logical name or class field for our tests, and passing along whatever technical details are necessary for creating the element. In these cases so far, everything has a unique R id value, so it’s nice and simple:

Screen model implementations — stupid simple.

It makes sense here to keep fields static ie., bound to the class. We’re never going to have multiple instances of a screen when we’re testing, so let’s not try and instantiate a screen object we won’t need. I love all-static classes. Keep it simple baby.

Second layer: PrismScreen

Our StartScreen is derived from PrismScreen, which uses genel(int rid) to generate instances ofPrismElement :

Attaching PrismElements to Screens.

Third layer: PrismElement

Most of the work gets done by the PrismElement. Espresso runs actions like click, setting text, etc through a ViewInteraction, so let’s include a view interaction inside PrismElement upon construction like so:

Instantiating PrismElements. Pass an android R.id, or a Hamcrest matcher.

We can then use the interaction to send actions like clicks and setting text:

How about some checks as well ?

You can see an open-source fork of the Omni Notes code with this Mobile Prism approach implemented here. It’s in active development and more articles are pending as we explore more technical interactions, while keeping things clean and compartmentalized.

Happy testing !

A software dev in test thinking against the grain. “To go faster, simplify, then add lightness.” ~Colin Chapman #cleancode #minaswan #innovate