Android Espresso tests are pretty unwieldy out of the box. Here’s a take on better architecture through layers.
Lessons from the Browser Age
Consider this functional web test for login:
# 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. As your application changes, you’ll have to update a lot of tests as locators and attributes change. Instead, web test automation tends to separate code into two (or more) parts:
- An abstraction layer which models the application under test.
- Business use cases which leverage page models execute the tests.
This abstraction layer is known as a “Page Object Model” or POM. A simple POM in Ruby might look like this:
# login_page.rb class LoginPage < SitePrism::Page element :username, “input#username” element :password, “input#password” element :submit, :xpath, “//button[@title=’submit’]” end
Special thanks here to the excellent Rubygem SitePrism.
Refactoring our login code with POMs would look thusly:
# login_test.rb LoginScreen.username.set(‘joe9’) LoginScreen.password.set(‘999’) LoginScreen.submit.click
Now that we’re using POMs, when our app changes, we only need to modify the element or page that changed. Easier to maintain, cleaner, more intuitive. It’s an established best practice in the browser world.
To the mobile, Alice
Let’s see if we implement a similar approach for Android with the Espresso toolkit. We’ll use the open-source app Omni Notes as our guinea pig.
Out of the box, here’s what it looks like when we create a new note using Espresso Test Recorder:
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.
How about something like this instead:
In this updated version, our test class is focused on describing the business use case. The actual work of running the test, we can now push into layers of complexity underneath, each layer having 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:
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 :
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:
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.
Happy testing !