Being based on XCTest, we are already familiar with the assert functions, so that we'll use an XCTAssertEqual function.
In order to retrieve the value in the topmost place, we have to find the right text field. XCUIApplication is a proxy to the interface of our app and it contains helper functions to access the elements.
XCUITest is based on the concept of queries; every element in the screen can be reached via a query and the script can send any kind of interaction, which will be executed at runtime. As we used app.buttons to reach the buttons, we'll use app.textFields. Being the text field we want to check, in the last position, we need to calculate its index position in the array, so we need to calculate its exact position:
func testAddition() {
let app = XCUIApplication()
app.buttons["2"].tap()
app.buttons["e"].tap()
app.buttons["3"].tap()
app.buttons["+"].tap()
let line0 = app.textFields.element(boundBy: app.textFields.count - 1)
XCTAssertEqual(line0.value as! String, "5.00")
}
However, relying on the position is fragile and not maintainable, and it also tightly couples the tests to the layout of the page, risking that changing the layout for some reason would break the tests. A better approach is to name the elements for the accessibility with an identifier, such as ui.textfield.line0. Every UIKit control has an accessibilityIdentifier property for this purpose, and the value can be set either in the storyboard or in the code. If we add it in the code, ViewController becomes the following:
class ViewController: UIViewController {
//..
@IBOutlet var line0: UITextField! {
didSet {
line0.accessibilityIdentifier = "ui.textfield.line0"
}
}
private let rpnCalculator:RpnCalculator = FloatRpnCalculator()
//..
}
The test now can search for the identifier:
func testAddition() {
let app = XCUIApplication()
// ...
let line0 = app.textFields["ui.textfield.line0"]
XCTAssertEqual(line0.value as! String, "5.00")
}
Identifying the elements makes the definition of the tests more robust and more maintainable, and it should be the first practice to adopt when preparing the code for a full testing strategy. Another improvement we could make to our test is to change the level of abstraction. The test relies on low-level elements, such as buttons or text fields, but it should use higher level concepts, such as tapOnZero, tapOnEnter, or valueOnTopMostPlaceOfTheStack. In this way, the code will be more readable, concise, and it will be easier to extend. To reach this goal, we can use the PageObject pattern, a useful testing pattern where each screen is wrapped in a class that exposes the service of the page.
In our example, a page object could be similar to this:
class CalculatorScreen {
private let app = XCUIApplication()
//...
func tapOnTwo() {
tapOnButton(title: "2")
}
func tapOnThree() {
tapOnButton(title: "3")
}
//...
func tapOnAdd() {
tapOnButton(title: "+")
}
func tapOnEnter() {
tapOnButton(title: "e")
}
//...
var valueOnTopMostPlaceOfTheStack: String {
guard let value =
app.textFields["ui.textfield.line0"].value as? String
else {
return ""
}
return value
}
private func tapOnButton(title: String) {
app.buttons[title].tap()
}
}
Using the CalculatorScreen object, the test is now written in a clearer way:
func testAddition() {
let calculator = CalculatorScreen()
calculator.tapOnTwo()
calculator.tapOnEnter()
calculator.tapOnThree()
calculator.tapOnAdd()
XCTAssertEqual(calculator.valueOnTopMostPlaceOfTheStack, "5.00")
}
We just scratched the surface of XCUITest, but with this basic information, we are ready to implement a full testing strategy for our apps.