Xcode UI Testing

Let's talk briefly about testing.

Xcode 7 is coming out with a great feature, XCTest UI tests. Now, some of you might not know, but we've been able to do UI testing for iOS apps for quite a while now using Instruments and the UIA Instrument. If tried that, you know that tool was terrible.

Now, UI testing is a fine approach to testing, but don't be fooled by the awesome demos – UI testing is the most expensive way to author tests. They are the hardest to maintain, they are the hardest to debug, and they are the slowest to run. Now, that does not mean that you should not write them, they just shouldn't be your primary way of testing your app.

Let's look at a simple test from the Lister demo app.

func testExample() {
    let app = XCUIApplication()
    app.tables.staticTexts["Groceries"].tap()

    let addItemTextField = app.tables.textFields["Add Item"]
    addItemTextField.tap()
    addItemTextField.typeText("Burgers")
    app.typeText("\r")

    XCTAssert(app.tables.cells.textFields["Burgers"].exists)
}

The test simply taps on the "Groceries" list, adds a "Burgers" item, and verifies that the item is indeed in the list.

Notice a problem?

Now, this is where we can get into a bit of a philosophical debate about testing and verification. The question is, do we want the UI tests to only verify that the UI is correct? Or, do we want our UI tests to validate that both the UI and the model is correct?

For me, I've seen far too many bugs where the UI was updated but the model didn't update to be satisfied with only validating the UI in our tests.

So, what do we do? The primary problem is that these UI tests are running outside of the process, and in the case of tests on the device, they aren't even running on the same machine. Don't worry, we have options!

The basic requirement is to be able to send a message of some sort to the application under test (AUT). Our options:

  1. Make use of the Accessibility APIs
  2. Make use of the network

My philosophy for building test infrastructure is to build it the cheapest, most simplest way possible for your needs at the time. When you have expensive test infrastructure, it can be a significant cost to update as your needs change later, and they always do.

So, instead of building up a mini-server that runs in your app, I would just fake it and create a UIView/NSView with a simple text field that's shown in response to some gesture. The purpose of this view is to provide a mechanism to provide a command layer into your product accessible via your test.

A command might even be as simple as a string: "get model items". When ENTER is pressed, you'd have a handler for the event that would handle each command and replace the contents of the text field with the result, such as: "Burgers, Apples, Oranges, Bananas, Milk, Bread".

Now, in your test, you can simply get the value of the text field and XCTAssert that the value matches the string.

You might be thinking that this is a bit of a hack, and well, you're right. However, it ends up being extremely cheap to maintain and update this model as it is so extremely easy to get up and running. Adding commands is literally as simple as registering the command name with a block on the view controller as well. And, if you really want to, you can compile out all of this stuff in App Store builds.

Xcode UI Testing