Testing. Testing. One. Two. Part 2: The Refactoring

Dec 09, 2018

In my last post I worked through an example of adding an abstraction later on top of UITest elements in XCode to make tests easier to deal with. Interacting with UITest can be cumbersome and a bit trial and error. To avoid the headache, the goal was to use Protocol Oriented Programming to expose only what’s necessary in the UI and interact with it in a way that felt natural. I also wanted to be able to compose steps in a way that if I had to test multiple ways, I could. In this post, I talk briefly about the refactoring I did on the code. I refactored to accomplish 2 things

  1. Maintainability
  2. Composition I also added in some Behavior Driven Development wrappers that provide better context inside my UITests. A few things I had to clean up:
  3. Because XCUIElement allows for query results to be arrays. I had to refactor out UIElement State as it’s own protocol to avoid a lot of purposefully crashing methods on my Array Extension
  4. I had to progressively add XCUIElement features into the UIElement protocol. Now, most places where I used a UIELement, I now use a UXElement.

Refactored code:

I also wanted reusable test components that failed at the right level. To do this, I pass in the file and line from calling methods. Shout out to The Big Nerd Ranch for this trick.

So now the .wait function now looks like:

Now, if the code fails, it fails and shows that it was waiting for state. Last but not least, I refactored the user activities to a function inside the test.

You may have noticed something here. I’ve added in Behavior Driven Development into the mix here as part of the process. It’s really just syntactic sugar for the XCTContext.runActivity method. The three steps are always

  1. Wait for state (Given)
  2. Interact (When)
  3. Wait for change in state (Then)

I won’t go deep into the concepts, but I think it’s a great way to decide what to test.

User Story: As a User I want to secure my usage of the app so that my private information is never exposed. Behavior: Scenario: Successful Login Given: The User Enters a good username and password When: The User presses the login button Then: The UI should display the search tab

The code itself looks like:

Now my UITest output looks like:

**Test Case ‘-[NowThisUITests.NowThisUITests testLogin]’ started.**
**t = 0.00s Start Test at 2018–12–09 14:55:12.573**
**t = 0.07s Set Up**
**AppEnvironment(loginEnvironment: NowThisUITests.LoginEnvironment.local, logoutEnvironment: NowThisUITests.LogoutEnvironment.onLaunch, serverEnvironment: NowThisUITests.ServerEnvironment.mock)**
**t = 0.20s Open io.thecb4.NowThis**
**t = 0.23s Launch io.thecb4.NowThis**
**t = 3.11s Wait for accessibility to load**
**t = 4.50s Wait for io.thecb4.NowThis to idle**
**t = 6.05s User successfully logs in to app**
**t = 6.05s The login screen appears**
**t = 7.06s Checking `Expect predicate `exists == 1` for object “usernameTextField” TextField`**
**t = 7.07s Checking `Expect predicate `exists == 1` for object “passwordTextField” SecureTextField`**
**t = 7.12s The user attempts a successful login**
**t = 7.12s Tap “usernameTextField” TextField**
**t = 7.12s Wait for io.thecb4.NowThis to idle**
**t = 7.15s Find the “usernameTextField” TextField**
**t = 7.17s Check for interrupting elements affecting “usernameTextField” TextField**
**t = 7.18s Synthesize event**
**t = 7.30s Wait for io.thecb4.NowThis to idle**
**t = 7.54s Type ‘user@thecb4.io’ into “usernameTextField” TextField**
**t = 7.54s Wait for io.thecb4.NowThis to idle**
**t = 7.61s Find the “usernameTextField” TextField**
**t = 7.68s Check for interrupting elements affecting “usernameTextField” TextField**
**t = 7.68s Synthesize event**
**t = 8.03s Wait for io.thecb4.NowThis to idle**
**t = 8.27s Tap “passwordTextField” SecureTextField**
**t = 8.27s Wait for io.thecb4.NowThis to idle**
**t = 8.33s Find the “passwordTextField” SecureTextField**
**t = 8.36s Check for interrupting elements affecting “passwordTextField” SecureTextField**
**t = 8.37s Synthesize event**
**t = 8.47s Wait for io.thecb4.NowThis to idle**
**t = 9.03s Type ‘RidiculousPassword12345**’ into “passwordTextField” SecureTextField**
**t = 9.03s Wait for io.thecb4.NowThis to idle**
**t = 9.29s Find the “passwordTextField” SecureTextField**
**t = 9.32s Check for interrupting elements affecting “passwordTextField” SecureTextField**
**t = 9.33s Synthesize event**
**t = 9.56s Wait for io.thecb4.NowThis to idle**
**t = 9.61s Tap “LoginButton” Button**
**t = 9.61s Wait for io.thecb4.NowThis to idle**
**t = 9.64s Find the “LoginButton” Button**
**t = 9.67s Check for interrupting elements affecting “LoginButton” Button**
**t = 9.67s Synthesize event**
**t = 9.76s Wait for io.thecb4.NowThis to idle**
**t = 9.99s The movie table should show**
**t = 11.08s Checking `Expect predicate `exists == 1` for object “New Releases” StaticText`**
**t = 11.12s Tear Down**
**Test Case ‘-[NowThisUITests.NowThisUITests testLogin]’ passed (11.327 seconds).**

A lot more human readable (IMHO). I can now start to think about failure points and testing that splits out a good password versus a bad password. As far as composability. Now my test is composed of several scenarios.