Simplify your UI-Tests
Every day we find blog post about how developers can ensure they are writing Clean Code. We get it hammered into us:
Don't Repeat Yourself
So we don't do it within our app.
When it comes to testing code though, for some inexplicable reason, we start to ignore all the good practices we had. We copy & past code, add stuff we don't need and even name things simply "x".
This doesn't help us. Instead it creates quite some confusion and results later in the tests being a legacy system.
Treat your tests, like your production code!
But how do we apply this?
A few weeks ago I wrote about the different currently existing iOS UI-testing frameworks. Let’s have a look how the process of a test for logging in looks like and if we can improve it, when considering the above statement:
- start app
- Check if Login Screen is visible(this is the first screen shown)
- Find username textfield
- Enter username
- Find password textfield
- Enter password
- Find Login-Button
- Tap Login-Button
- Wait and check for an identifier to see if we are logged in (Test assertion)
- Cleanup (multiple steps containing logout)
As you can see even when condensing the required cleanup steps, we are still left with 9 steps. This is a lot and now imagine having an app, which requires being logged in for any kind of functionality. This will become a setup step!
Having to write all these steps every time we login is quite hideous. As always, there is a pattern trying to make things easier. This pattern from the world of web development is called Page Objects. Since we do not separate our screen into smaller elements (which you could do btw) let’s call this pattern Screen Objects in the mobile world.
Screen Objects describe are a step further into Object-Oriented Development. They describe data and possible interaction on a screen. When navigating to other screens, they will return new Screen Objects concerning the new screens. Be aware, an easy failure is to try to add state or functionality, which is not visible to the user. Don’t forget, it’s about what the user can see, and not about what we know the app is capable of!
Consider this login screen:
The elements I’ve described in the steps above are:
- Some kind of Identifier (our flag with accessibility identifier)
- 2 Textfields
- 1 Button
In code this could look:
I've used a small extension to prettify entering text into a text field:
As you can see, we initialize the screen with the app. This is necessary, as it needs access to the different elements. Otherwise, it wouldn’t be able to interact with the app at all.
Next, it provides a few methods for interacting with the screen. Enter(username: String) takes care of tapping into the username text field and entering the provided username. Enter(password: String) does the same, but with the password text field. Lastly, we have tapLogin(). This taps onto the login button and returns a new screen object for the next screen. I’ve kept this ambiguous on purpose, as I didn’t want to make any assumptions, about which feature will follow in your app.
You can also add convenience methods. For example login(withUsername:password) improves readability and you don’t have to write down every single step for a login.
Sooo... why should we use Screen Objects? They look like quite a hassle to implement and I get quite far just writing the same steps over and over again.
The first and foremost reason to use Screen Objects is to abstract test boilerplate code. You don’t want to write down every single step the test has to do. Instead, you want to be a little bit more declarative (No I won’t start an imperative vs. declarative war here!).
Having this abstraction your interface can change as much as it wants. You just need to update the Screen Object and you’re done. All the actual testing code stays the same.
It’s a new pattern you’ll have to understand. Sometimes this is easy, sometimes it’s hard. There are discussions if Page Objects should contain assertions. As Screen Objects are basically identical, the same question applies. I would advice against assertions within the screens, but this is for you to decide. Implementing this pattern requires the YAGNI principle. If you don't need it, let it be. It will just be clutter and you won't use.
Writing UI-Test is most often no fun. It’s annoying to describe every step taken and even more if you have to do it multiple times. Screen Objects can help you to lessen this burden. They won’t take away everything, but even nondevelopers can suddenly write tests for your app. It doesn’t matter, which UI-testing framework you use, I would advise you to use Screen Objects. Even if it’s just for not having to write code multiple times!
Next: Golden Master Testing
Previous: Mock Network Requests in UITests