Striking a Balance With UI Tests - ConnectTech

23
Striking a Balance with UI Tests By: Jesse Black Software Engineer stable|kernel [email protected]

Transcript of Striking a Balance With UI Tests - ConnectTech

Page 1: Striking a Balance With UI Tests - ConnectTech

Striking a Balance with UI TestsBy: Jesse BlackSoftware Engineerstable|kernel [email protected]

Page 2: Striking a Balance With UI Tests - ConnectTech

@stablekernel

Hi, I’m Jesse Black.

• Programming for over nine years• Created a Mac App for my family business• Worked for 3 years with Gramercy Consultants developing iOS and Android apps• Working for stable|kernel for the past 4 years developing iOS apps, Android apps

and their supporting APIs

Page 3: Striking a Balance With UI Tests - ConnectTech

We’re stable|kernel. stable|kernel is an Atlanta-based mobile development company to craft smartly-designed mobile applications that connect brands directly with their users.

Page 4: Striking a Balance With UI Tests - ConnectTech

Striking a Balance with UI Tests

@stablekernel

Overview

• General Testing• Demo: Introducing UI Tests• Improving UI Tests with Robots• Mocking Dependencies• Discussion

Page 5: Striking a Balance With UI Tests - ConnectTech

Types of Tests

@stablekernel

• Manual testing• Automated testing• Unit / Integrated / End to End

• Continuous Testing

Page 6: Striking a Balance With UI Tests - ConnectTech

Unit Tests

@stablekernel

• Tests a small amount of code with limited inputs and output• Should be F.I.R.S.T.

• Fast• Isolated• Repeatable• Self - Verifying• Timely

Page 7: Striking a Balance With UI Tests - ConnectTech

Why test?

@stablekernel

• Fast feedback• Help prevent regressions• Improve code design

Page 8: Striking a Balance With UI Tests - ConnectTech

Demo

@stablekernel

UI Test Recording and Playback

Page 9: Striking a Balance With UI Tests - ConnectTech

Recording observations

@stablekernel

• Black box• User’s perspective• No Asserts (Assertion by tapping)

Page 10: Striking a Balance With UI Tests - ConnectTech

Example: Login Error

@stablekernel

let app = XCUIApplication()let usernameTextField = app.textFields["Username"]usernameTextField.tap()usernameTextField.typeText("invalid")app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 0).tap() let passwordSecureTextField = app.secureTextFields["Password"]passwordSecureTextField.tap()passwordSecureTextField.typeText("invalid")app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 0).tap()app.buttons[“Login"].tap() app.alerts["Username and password combo not found."].buttons["OK"].tap()

Page 11: Striking a Balance With UI Tests - ConnectTech

Example: Login Error

@stablekernel

let app = XCUIApplication()let usernameTextField = app.textFields["Username"]usernameTextField.tap()usernameTextField.typeText("invalid")app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 0).tap() let passwordSecureTextField = app.secureTextFields["Password"]passwordSecureTextField.tap()passwordSecureTextField.typeText("invalid")app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 0).tap()app.buttons[“Login"].tap() app.alerts["Username and password combo not found."].buttons["OK"].tap()

• Scary strings• Will fail without network• Chunks of very similar code• Fragile tap to dismiss lines

Page 12: Striking a Balance With UI Tests - ConnectTech

Wrangle user facing strings

@stablekernel

• Localize all user facing strings• Add localized string file to both targets; app and UI Test targets• Create abstraction around NSLocalizedString to avoid using stringly

typed keys when accessing user facing strings

Page 13: Striking a Balance With UI Tests - ConnectTech

Wrangle user facing string

@stablekernel

enum UIString: String { case usernamePlaceholder = "username-placeholder"

func localized() -> String { return UIStringsHelper.getLocalizedString(key: rawValue) }}

private class UIStringsHelper { static func getLocalizedString(key: String) -> String { let bundle = Bundle(for: UIStringsHelper.self) return NSLocalizedString(key, bundle: bundle, comment: "") }}

Page 14: Striking a Balance With UI Tests - ConnectTech

Factor out user actions into Robots

@stablekernel

struct LoginRobot { let app: XCUIApplication

var usernameTextField: XCUIElement { return app.textFields[UIString.usernamePlaceholder.localized()] }

func dismissKeyboard() { app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element(boundBy: 0).tap() }

func enterUsername(text: String) { usernameTextField.tap() usernameTextField.typeText(text) }

}

Page 15: Striking a Balance With UI Tests - ConnectTech

Example: Login Error Refactored

@stablekernel

func testInvalidCredentials() {loginRobot.enterUsername(text: "invalid@email")loginRobot.dismissKeyboard()

loginRobot.enterPassword(text: "invalid")loginRobot.dismissKeyboard()

loginRobot.loginButton.tap()

let alertRobot = AlertRobot(app: XCUIApplication(), title: "Username and password combo not found.")alertRobot.dismiss(withButtonTitled: UIString.okButton.localized())

}

Page 16: Striking a Balance With UI Tests - ConnectTech

Dependencies

@stablekernel

• Data persistence• Networks• Third Party Libraries

Page 17: Striking a Balance With UI Tests - ConnectTech

Mock Out Dependencies

@stablekernel

Page 18: Striking a Balance With UI Tests - ConnectTech

Mock Example: Login Credentials

@stablekernel

// UI Test Setupoverride func setUp() { super.setUp()

// ...

XCUIApplication().launchArguments = [“USE_MOCK_LOGIN_STORE”, “HAS_SAVED_USERNAME”] XCUIApplication().launch()}

Page 19: Striking a Balance With UI Tests - ConnectTech

Mock Example: Login Credentials

@stablekernel

protocol LoginCredentialsStore: class { func saveUsername(username: String?) func readUsername() -> String?}

Page 20: Striking a Balance With UI Tests - ConnectTech

Mock Example: Login Credentials

@stablekernel

class MockLoginCredentialsStore: LoginCredentialsStore {

var savedUsername: String?

init() { if CommandLine.arguments.contains("HAS_SAVED_USERNAME") { savedUsername = "[email protected]" } }

func saveUsername(username: String?) { savedUsername = username }

func readUsername() -> String? { return savedUsername }}

Page 21: Striking a Balance With UI Tests - ConnectTech

Realistic Goals

@stablekernel

• Automate screen shots of your app as you roll out changes• Be prepared for demos with stakeholders as you close out units of work

• Increase confident that previously demoed work does not have any regressions

Page 22: Striking a Balance With UI Tests - ConnectTech

Final Remarks

@stablekernel

Page 23: Striking a Balance With UI Tests - ConnectTech

Questions?

Business Inquiries:Sarah WoodwardDirector of Business [email protected]

Jesse BlackSoftware [email protected]@JesseBlack82