Tech Showcases,
Developer Resources &
Partners
droidcon News
Espresso Patronum - The Magic Of The Robot Pattern
By
Adam McNeilly
droidcon London 2019
Transcript
English
00:00
can everyone hear me okay cool so I'm
00:04
Anna McNeely I am a senior Android
00:06
engineer for a company called OkCupid
00:08
and today we are going to talk about the
00:11
robot pattern
00:12
more specifically what makes it so
00:14
magical and how it could help you write
00:16
better more maintainable espresso tests
00:19
when it comes to the robot pattern I
00:21
found the easiest way to explain why
00:23
it's helpful is to make sure we
00:25
understand the problem it solves first
00:27
so the way this talk is structured is
00:29
we're gonna explain espresso for any of
00:30
you who are unfamiliar with it show you
00:33
the problems you have when you just
00:35
follow the docs on espresso and then how
00:37
the robot pattern is gonna fix all of
00:39
those problems for you if you're
00:42
unfamiliar with espresso this is a UI
00:44
library UI testing library from Google
00:47
that allows you to write concise
00:48
beautiful and reliable Android UI tests
00:51
so if you have a feature in your app
00:53
even as simple as your login page and
00:55
every time you change your app and you
00:58
want to make sure the login page still
00:60
works to run the APK type in a username
01:03
password hit sign in make sure you get a
01:05
new page if you've ever wanted to
01:07
automate that entire flow have something
01:09
that will just type text into the phone
01:11
for you and click the buttons that's
01:14
what you would use espresso for when it
01:19
comes to developing your espresso tests
01:22
there are three classes that you need to
01:24
worry about few matress do actions and
01:28
view assertions and we're going to talk
01:30
about each of those view matters our
01:32
methods we call when we want to find a
01:35
view on the screen that matches some
01:37
conditions so if I want to interact with
01:40
a view that has a given ID I call with
01:42
ID R that ID that whatever or you can
01:46
find views that have a certain text that
01:48
you specify is it a focusable view is it
01:53
a checked view all sorts of conditions
01:56
like that but it's fine the view that
01:58
matches these conditions and then we
02:02
have our view actions and these are
02:04
called when we want to interact with a
02:07
view on the screen once we found it so
02:09
that could be typing text and through an
02:11
edit text
02:12
it could be scroll into a view if it's
02:14
not on the screen or scrolling to a
02:16
position in the recyclerview maybe
02:19
swiping left on a view or clicking on a
02:21
button and then we have view assertions
02:25
and these are for scenarios where I
02:28
found a view on the screen and now I
02:29
want to make sure that it matches some
02:32
conditions I have and there's a method
02:35
for that where you would just say
02:36
matches so what this is used for is I
02:39
might try to find a view on the screen
02:41
and I want to say that it matches that
02:43
it has some text inside of it you could
02:47
do is left out or is right as this
02:50
doesn't happen often but worth calling
02:52
out that it's there or maybe that a view
02:55
does not exist what so you have a layout
02:57
file that's different and say portrait
02:59
or landscape or phone versus tablet this
03:02
method is really helpful for making sure
03:04
that something's not created in a
03:06
certain layout and every espresso tests
03:11
sort of follows this simple pattern we
03:13
call on view with some view matcher
03:16
which gives us something called a view
03:17
interaction which we can then either
03:19
perform an action or check some
03:22
assertion a more specific example let's
03:26
say we have an app that you type some
03:28
text into an edit text and as you do
03:31
that it updates the textview on the
03:33
screen and do you want to make sure
03:34
using espresso that that works so the
03:37
first thing you'll do is call on view
03:38
with ID our that ID eat the input or
03:42
whatever you're out of Texas so you want
03:44
to perform an action typing for the text
03:46
and then entering your name and then you
03:49
want to find the view with the ID TV
03:51
output and check that it matches with
03:54
text Adam there's a lot of nesting here
03:57
but I think espresso does a good job of
03:58
clearly naming their methods so that as
04:01
we go through this left to right we can
04:03
kind of understand what each line is
04:05
doing something I'm going to work
04:09
through in this talk is to demonstrate
04:10
how we would write it as a test to fill
04:13
out a basic form like this so in this
04:15
sample we have a form that has first
04:17
name last name email phone number and
04:19
whether or not you are not in the emails
04:21
sort of simple registration form when
04:24
you click register it takes
04:25
a new screen that just shows you your
04:28
user details but they're also formatted
04:30
your first and last name are combined
04:32
together your format is phone is your
04:34
phone number is formatted with
04:36
parentheses around it and such and what
04:39
you see in this gif is actually an
04:40
espresso test running I'm not typing any
04:43
of this in myself this is all automated
04:47
now I want to put a little disclaimer in
04:49
before I go to the next slide because
04:50
it's very code heavy and I normally
04:53
don't like to do that my talks I don't
04:54
want to lose anyone but I did that on
04:56
purpose because I want you to see how
04:58
much code is involved in this but I
05:00
promise the whole talk isn't like that
05:01
so don't give up on me just because of
05:03
this one slide they use all of the code
05:06
for that happy path test we're on the
05:10
very first line we call our view with ID
05:12
easy first name perform type text Adam
05:14
and then we have to do the same thing on
05:16
the next one to type in my last name
05:17
email phone number and then we click and
05:20
then the bottom three lines are checking
05:23
that the output matches what I'd expect
05:26
it to be and this is a lot right this is
05:30
a lot of repetitive typing who makes it
05:32
easy to make mistakes because I'm
05:34
probably just gonna copy and paste from
05:36
line to line honestly when I first open
05:40
up this file and I look at this test
05:41
like I'm immediately taken aback because
05:43
I like don't know what's happening
05:47
because the lines are so long and I have
05:49
to read left to right all the way to
05:51
figure out that oh I'm typing Adam into
05:54
the first name edit text by the time I
05:56
have read the like seventh line in this
05:59
file are in this class method I've
06:02
probably already forgotten what it's
06:04
doing so that's just something to keep
06:06
in mind and it's also hard to discern
06:09
when you test leaving out a field like
06:12
that I've actually done that thankfully
06:15
I have like a helpful method name here
06:17
test missing email error and I can see
06:20
on the bottom that I'm checking for
06:21
error text but this is really sort of
06:24
indiscernible from the test before that
06:27
and again I might just copy and paste
06:30
things from the previous test and I'm
06:32
more likely to make a mistake because of
06:33
it maybe I've forgotten to change one of
06:36
the IDs or what I'm typing into it
06:39
and this is just another example of an
06:41
invalid field but then you throw that
06:44
all together and you just get this
06:45
really sort of large collection of
06:48
methods that are all blurred together
06:49
and they're really difficult to read
06:53
which is everything I'm going to talk
06:55
about the problem with this is this is
06:57
very verbose there's a lot of code
06:59
required to explain what my test is
07:01
doing and it's doing so in a way that's
07:04
not readable or not easy to read for
07:06
someone who didn't write the test
07:08
themselves it takes a long time to come
07:10
in and read those methods and understand
07:12
what they're actually testing but more
07:16
importantly beyond all the readability
07:18
concerns of this is these are difficult
07:20
to maintain because we have no
07:22
separation of concerns with our UI tests
07:27
highlight that let's consider how we
07:29
would consider separating concerns
07:31
inside our app itself you may use
07:34
model-view-presenter pattern or whatever
07:37
pattern you're familiar with but we do
07:39
that because the model has its own
07:40
responsibilities and it talks to the
07:43
presenter and if I ever want to change
07:45
my model say from a network to a
07:47
database ideally this is all behind one
07:50
interface where I can just swap out the
07:52
model and not have to change the
07:54
presenter right and that all seems fine
07:57
if you look on the right we have one
07:59
espresso test and our espresso test is
08:02
interacting with our view as you just
08:04
saw and that's fine if we have one
08:06
espresso test but ideally we had a lot
08:08
more than that and what will happen is
08:12
if you change something about your view
08:14
let's say some input changes from I
08:16
don't know like some edit text to maybe
08:19
you can make it a spinner or it becomes
08:21
like a date picker or something like
08:24
that that's going to break all of your
08:26
tests and all of your tests are going to
08:29
need to be updated even if it's as
08:31
simple as you change the identify out of
08:33
view if you don't use like Android
08:34
studios refactor you have to go into all
08:36
of your UI tests and change that
08:38
identifier so the solution to that
08:41
because we can't just wrap espresso
08:43
tests around an interface is to
08:46
introduce a new component in the middle
08:48
call it a robot your UI tests then know
08:52
how to interact with the robot and the
08:54
robot knows how to interact with the
08:56
view and so in the view changes the
08:59
robot changes and so this saves you a
09:02
lot of time when you go to change your
09:05
view and that you could do so in a way
09:07
that you know is not going to break all
09:09
of your tests which is really helpful
09:11
when I was bouncing around ideas about
09:13
this talk with a friend they told me
09:15
that tests discourage refactoring
09:18
because if your tests work a certain way
09:20
and you know that changing the UI is
09:22
gonna break all of your tests you don't
09:24
want to do it or you change the UI and
09:26
then you just delete all the tests
09:27
because they don't work so hopefully
09:30
this will help you fire up a solution to
09:31
that before we explain implementing a
09:36
robot though let's take a look at what
09:38
that would look like when we're done so
09:41
ideally we could just write a UI test
09:44
that is way more readable so this is
09:48
successful registration I created a
09:50
registration robot when I tell the robot
09:52
enter this first name and to this last
09:54
name email phone number click register
09:56
and then assert all of these outputs and
09:60
what I really like about this is this
10:02
allows you to write your tests as if
10:04
you're trying to tell a quality
10:06
assurance engineer what to do if you
10:08
were handing off your phone to QA to
10:10
test your new feature you're not gonna
10:12
tell them find the view with this ID
10:14
then perform the action of typing text
10:16
like that doesn't make any sense you're
10:18
going to tell them to do these steps so
10:21
that's a lot nicer it makes you test a
10:23
lot more readable and a lot more
10:24
composable which we'll see in a couple
10:27
slides so the first thing I do when I'm
10:32
creating a robot which is just a class
10:34
called registration robot is I throw all
10:37
of my view matches into a companion
10:39
object or if you're still using Java
10:41
these are just static fields but this is
10:44
every way to like find the views you're
10:46
interacting with because I might have
10:48
multiple methods later that interact
10:50
with first-name whether I'm inserting
10:53
text into them or invalidating the text
10:54
in them we'll throw whatever view
10:57
manager we have into one single place
11:00
then inside a robot we want to write one
11:03
method for each action that we want to
11:06
do and
11:06
screen so here a couple examples right a
11:09
method called first name and you could
11:11
call this and her first name maybe it's
11:13
more clear but I didn't think of that in
11:15
time to update the slides the first name
11:18
method returns a registration robot and
11:20
you can see that it just calls returned
11:23
this that's just the Builder pattern
11:25
that's what allows us as we saw to chain
11:27
these calls together and then we can go
11:30
and put our espresso or a robot methods
11:33
in any order we want and easily sort of
11:36
like plug and play with all of these
11:38
robot methods and so inside first name
11:41
we find a view with our first name
11:43
matcher that we defined earlier and then
11:46
perform some actions here is just a
11:48
little best practice I snuck in here
11:50
if you're writing espresso test for any
11:52
inputs try to clear the text
11:55
ideally there was nothing there before
11:57
but just in case type your text and then
11:59
close the keyboard if you don't do that
12:01
the keyboard can stay open and that can
12:03
sometimes break later steps and then
12:07
register works the same way it returns a
12:09
registration robot so I can put this
12:11
anywhere in my call inside the test I
12:14
find a view with the register
12:16
implementer I don't know why I call it
12:18
input but then we perform an action and
12:21
we click on it similarly we'll write one
12:25
method for each associate so I'll write
12:28
a method called a certain email display
12:29
give it the email I expect and then
12:32
within there I put it on do email
12:34
display matcher check matches with text
12:36
email I'm not going to be able to
12:39
completely get rid of this complicated
12:41
espresso code that has these really long
12:43
lines with nested methods but at least
12:46
here I can abstract it away from the
12:48
test file and make the test more
12:51
readable and now it's much easier to
12:54
understand a certain email display and
12:56
all the espresso code inside of it then
12:58
when I had seven lines of espresso
13:00
assertions back-to-back one small
13:03
difference here if you're using Java
13:05
instead of or if you're using Kotlin
13:08
instead of saying this returns
13:10
registration robot you can set this
13:11
equal to apply and it's basically the
13:14
same thing but it's just a little
13:16
cleaner less code
13:19
and then here's our implementation again
13:23
so again the nice thing about the
13:25
Builder pattern is this is really easy
13:27
to compose tests I've written a method
13:29
for every action I can take for all of
13:31
my inputs and I've got one for
13:33
everything that I want to assert and so
13:35
now if I want to take this and try to
13:38
make a negative test that's really easy
13:41
I just drop the email call and then put
13:44
a certain email error at the very end I
13:46
didn't have to copy and paste centum
13:49
liens find the right you know input ID
13:53
and change that this just becomes way
13:55
more composable and what you get out of
13:57
this is I've learned from a lot of
13:59
people that writing espresso tests takes
14:01
a lot of time so they might write their
14:03
happy path they might write one or two
14:05
air scenarios but if you take the time
14:08
to build out your robot to do everything
14:10
it's way more easier to test all sorts
14:13
of permutations of this form so if I
14:15
want to write a test for each of the
14:17
individual fields if I left it blank
14:19
that becomes way more easy to do with
14:21
something like this I'm again in Catalan
14:26
just a fun fact if you want to work some
14:28
Catalan magic you can write a
14:30
higher-order function called like
14:33
registration I haven't taken a function
14:35
that interacts on the registration robot
14:36
and then you can get this cool test at
14:39
the bottom where instead of all of the
14:41
Builder pattern calls would still look
14:42
very java asked you can write
14:45
registration put some brackets around it
14:46
and then call all of your methods in
14:48
there that was just a fun fact to
14:51
include if you're not familiar with it
14:54
so that's everything about why you need
14:59
a robot pattern how to implement one
15:02
what I want to share best practices I've
15:05
learned over time using the robot
15:07
pattern one really cool thing that you
15:12
can use the robot pattern for is
15:14
leveraging it for better test reporting
15:16
so if you want to write UI tests that
15:20
take a screenshot at every step or even
15:22
log that step somewhere because you've
15:25
already built a robot and you have every
15:27
step your test takes defined as its own
15:29
method you can just put that log in your
15:31
screenshot step
15:32
right inside that method this is way
15:35
more difficult if you had 12 different
15:37
full-length tests that are filling out
15:40
the form and then do you want to go put
15:42
a screenshot every time you enter the
15:44
first name if the copy and paste those
15:45
12 different types here you get to just
15:48
interject it once and that's really cool
15:51
and if you're interested in that I
15:52
recommend Sam Edwards droidcon NYC 2016
15:56
talk AB screenshots with espresso which
15:58
is where I first learned about the robot
15:60
pattern which he talked about for a few
16:02
minutes because of screenshots and I
16:04
realized there was way more to it then
16:05
he gave her credit for I also get a lot
16:10
of questions about navigation when it
16:12
comes to robots if you're navigating
16:13
from one screen to another when you do
16:16
that you want to use one robot per
16:18
screen so here we have a registration
16:21
robot that knows how to fill out the
16:23
form and they click register and then
16:25
when that goes to our user profile page
16:27
we want that to run all of the user
16:29
profile assertions and I need possible
16:31
actions that are on that screen and the
16:36
reason for that is it might sound nice
16:38
to chain robots together like when I
16:40
click register I know that's gonna take
16:43
me to the user profile screen so why not
16:45
just have register returned to user
16:47
profile robot but then we've coded
16:50
yourself into a corner where you're
16:51
unable to do negative tests so if I
16:53
don't fill out something in the form I'm
16:55
not supposed to go to the next screen so
16:58
if register returns me a user profile
17:00
robot then I can't call my assert email
17:03
error method any boy so that's just
17:06
something to keep in mind
17:07
it sounds nice to have that smooth
17:09
transition but we won't be able to write
17:12
any negative tests and that'll cause you
17:14
a lot of problems I also don't recommend
17:18
putting conditional logic in your robot
17:21
methods so it also might sound nice to
17:23
write one like assert the opt-in status
17:26
and pass in whether or not you expect
17:28
the user to be opted into emails and
17:30
then within there you find out okay if
17:33
they're after date I want is checked if
17:35
they're not I want is not checked and
17:38
then assert that the opt-in checkbox
17:41
that's a checkbox their matches
17:43
matter and why little work you just you
17:48
start putting this conditional logic in
17:49
here now you're wondering well who test
17:51
the test and if this doesn't work is it
17:54
because I have a bug in my app or did
17:56
this if statement break so if you're
17:59
going to do something like that just use
18:00
the separate methods create one called
18:02
assert app that in have that check for
18:05
it is checked and create one called
18:07
a certain opted out and have that check
18:09
for it's not checked now we've talked
18:16
about robot patterns in the context of
18:18
espresso and that's really helpful
18:21
because if our view changes we want all
18:23
of our tests to still work and we've got
18:28
this more or less ugly espresso code
18:31
that we're trying to hide behind
18:32
something more readable but actually
18:35
nothing I've talked about here with the
18:36
robot pattern is specific to UI testing
18:39
you can use this pattern inside your
18:42
unit test as well and you get all of the
18:44
same benefits of having more readable
18:46
tests and tests that are way easier to
18:48
update if you change one of your classes
18:51
and we're gonna walk through both of
18:53
those so here I want to show a view
18:58
model class that I want to unit test I
19:00
have a view model that takes in a
19:02
repository for fetching a user the view
19:06
model exposes some network state as a
19:08
live data and I have a method called
19:11
fetch user which will fetch the user
19:13
from the repository if it's successful
19:15
we're gonna set our state to load it if
19:18
it's not we're gonna set our state to
19:19
error
19:20
so let's write a unit test for that
19:23
prior to the robot pattern you may write
19:26
something like this we create a mock
19:28
repository using makino or mock kay or
19:30
whatever mocking library you like create
19:33
a sample user that we're going to return
19:35
and say whenever we factually user for
19:38
remark repository return this sample one
19:41
that I just created then we create our
19:44
view model with that Mac repository we
19:46
call fetch user which will just fetch
19:50
that user but because we're in the unit
19:52
test mocking it it's just gonna fetch
19:53
that sample user we created we get a
19:56
reference to the
19:57
current state this test observer is my
19:59
old like extension method and live data
20:02
to get like the latest value from a live
20:05
data in a unit test and then I'm going
20:07
to assert equals that Network States
20:09
loaded is equal to the current state and
20:13
I have all the same problems here when I
20:14
look at this test
20:16
I can't just look at it and within like
20:18
a few seconds figure out what's doing I
20:19
almost have to read every single line to
20:22
understand unless I write a really good
20:24
method name but here successful fetch I
20:27
don't know how helpful that is and we'll
20:32
look at this example in a second but to
20:35
also talk about the separation of
20:36
concerns here if I write a lot of tests
20:39
with this if I change my view model
20:42
state to be some rx type and not a live
20:45
data or whatever else I'm gonna have to
20:48
update every single one of these unit
20:50
tests so there's still a separation of
20:52
concerns problem here and our testing
20:55
well we can solve that by creating a
20:58
robot here too so here we have the user
21:00
view model robot it creates a block
21:03
repository and our view model using that
21:05
for us we have a method to mock the user
21:08
response so our robot knows how that
21:10
marking is supposed to happen
21:12
fetch user will just proxy that call
21:14
through into the view model and then we
21:16
have assert state where we tell it what
21:18
state we want to have and the robot is
21:21
responsible for getting that state from
21:23
the view model and then asserting that
21:25
they're equals implementing that robot
21:30
is pretty much the same we have a
21:33
successful fetch method at the bottom
21:35
that creates our sample user so the test
21:38
is still going to have some
21:39
responsibilities and I think that should
21:41
be like setting up the data that you
21:43
want so the test is responsible for
21:45
setting up here's our sample user that
21:47
we're going to fetch but then I could
21:49
just call test robot my user response
21:51
fetch user and then assert the state
21:54
there's something I also didn't hear was
21:56
I put it in our test robot as a late
21:60
init variable and then inside the set up
22:02
method I create the robot that's really
22:05
good because we want one robot for every
22:07
test you may get in a scenarios where
22:10
you
22:10
robot might have some kind of state
22:12
management in it and then you don't want
22:14
that state to bleed over from one test
22:16
to another so either use something like
22:19
this where you give it a new value every
22:21
time setup is called which anjuna is
22:23
called before each test or alternatively
22:26
inside the method here where I call test
22:30
robot I could just put the constructor
22:33
there myself but I like having one thing
22:35
that is easier to reference the loop
22:38
stick and so again now with my unit test
22:43
this becomes way easier to go in add an
22:45
error test I just need to add one day
22:48
method to my robot that's Mac user error
22:50
and then I could write a failure fetch
22:53
method by just plugging in that new
22:56
method into a builder pattern so let's
23:05
see this in action we've gone through
23:07
all the effort of creating a robot and
23:09
we've talked about it at a high level
23:11
like why this is helpful but let's look
23:14
at a real world example where this is
23:16
going to save you a lot of time so right
23:19
now I have two methods in here that
23:21
fetch like a successful fetch and a
23:25
failure fetch right and then I go
23:28
through loop okay oops okay I went too
23:32
far forward for whatever reason I
23:36
decided I'm gonna update my view model
23:38
and instead of exposing state as a live
23:40
data I'm gonna make it a behavior
23:42
subject and now previously I would have
23:47
to go into each of my successful fetch
23:49
and my failure fetch methods and any
23:51
other view model tests I have where um
23:53
the real world I might have dozens of
23:55
you view model tests and I'm gonna have
23:57
to change the way that I compare state
23:60
right not with this we just go into our
24:03
one robot method and instead of calling
24:05
the test observer I could just get the
24:07
state right for the behavior subject and
24:10
then I start those are equal and by
24:12
changing one line of my view model and
24:14
one line of my robot all of my tests
24:16
still pass so again there's two but
24:20
think about how many tests you hopefully
24:22
have and you've saved yourself
24:24
so much time as a result so let's recap
24:30
all of that utilize the robot pattern
24:34
for writing more readable and more
24:36
maintainable tests those are the two
24:38
biggest perks I think you get out of the
24:40
robot pattern but take advantage of it
24:44
if you want better test reporting
24:45
whether that's screen shot on your UI
24:47
test or maybe just logging to the
24:49
console so you understand what each test
24:51
is doing you pretty much get that for
24:54
free because you've already made one
24:56
method for every step you're gonna take
24:58
and make sure you don't code yourself
25:01
into a corner with additional complexity
25:03
by that I mean don't shave new robots
25:05
together and don't include any logic
25:08
inside these robot methods and while we
25:11
talked about UI testing as the biggest
25:15
use case for this and partially because
25:17
that's the way I was introduced to it
25:18
it's not specific to UI testing and it's
25:21
also not specific to espresso if you're
25:23
using any other UI testing framework the
25:26
same concept will still apply with those
25:28
as well and that's all I've got
25:32
if you want to see a sample app with
25:34
this as well as the slides I have a
25:36
github repo here
25:37
which shows what you saw earlier with
25:39
answering all the text into a form
25:41
clicking enter seeing a new page on that
25:44
sample repo I actually do include
25:46
screenshots if you want to see how
25:50
something like that would work so thank
25:52
you
25:59
I'll hang around up here if there are
26:01
questions I'd rather just do them up
26:02
here Thanks
droidcon News
Tech Showcases, Developer Resources & Partners
EmployerBrandingHeader
jobs.droidcon.com
![]() Latest Android Jobs
Kotlin Weekly
![]() Your weekly dose of Kotlin
ProAndroidDev
![]() Android Tech Blogs, Case Studies and Step-by-Step Coding
Zalando
![]() Meet one of Berlin's top employers
Academy for App Success
![]() Google Play resources tailored for the global droidcon community |
Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.