LoginSignup
6
2

More than 3 years have passed since last update.

[iOS] XCUITestをPageObjectパターンで実装してみた

Posted at

はじめに

iOSを開発し続ける中で、UITestを今までほぼ書いてこない開発人生を送ってきました。iOSDC2020でXCUITestのつらさを乗り越えて、iOSアプリにUITestを導入するの発表を見て、書いてみようと思い調べ実装したものをまとめました

開発環境

Xcode 12.4
Build version 12D4e
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0

PageObjectパターンとは

実装してみた

発表のコードを参考にprotocolの定義します


import Foundation
import XCTest

protocol PageObjectable: AnyObject {
    associatedtype Ally
    var app: XCUIApplication { get }
    init(app: XCUIApplication)
    var exists: Bool { get }
    var pageTitle: XCUIElement { get }
    func elementsExist(_ elements: [XCUIElement], timeout: Double) -> Bool
}

extension PageObjectable {
    var app: XCUIApplication {
        return XCUIApplication()
    }

    var exists: Bool {
        return elementsExist([pageTitle], timeout: 5)
    }

    func elementsExist(_ elements: [XCUIElement], timeout: Double) -> Bool {
        for element in elements {
            if !element.waitForExistence(timeout: timeout) {
                return false
            }
        }

        return true
    }
}

操作したいUIに対して、accessibility identifierをセット

  • codeでもIBでもどちらでも設定できます

スクリーンショット 2021-03-09 8.32.35.png

スクリーンショット 2021-03-09 8.29.29.png

Page Objectを定義する

import Foundation
import XCTest

final class LoginPage: PageObjectable {
    let app: XCUIApplication
    init(app: XCUIApplication) {
        self.app = app
    }

    var pageTitle: XCUIElement {
        return app.navigationBars[Ally.title].firstMatch
    }

    var emailTextField: XCUIElement {
        return app.textFields[Ally.emailTextField].firstMatch
    }

    var passwordTextField: XCUIElement {
        return app.secureTextFields[Ally.passwordTextField].firstMatch
    }

    enum Ally {
        static let title = "Log in"
        static let emailTextField = "emailTextField"
        static let passwordTextField = "passwordTextField"
    }

    @discardableResult
    func typeEmail(_ email: String) -> Self {
        _ = emailTextField.waitForExistence(timeout: 5.0)
        emailTextField.tap()
        emailTextField.typeText(email)
        return self
    }

    @discardableResult
    func typePassword(_ password: String) -> Self {
        _ = passwordTextField.waitForExistence(timeout: 5.0)
        passwordTextField.tap()
        passwordTextField.typeText(password)
        return self
    }

    func goToSuccessPage() -> SuccessPage {
        let rightNavBarButton = app.navigationBars.children(matching: .button).firstMatch
        _ = rightNavBarButton.waitForExistence(timeout: 5.0)
        rightNavBarButton.tap()
        return SuccessPage(app: app)
    }
}

UITestを書く

  • あとはテストしたい処理をXCTestCaseのクラスに書きます
final class XCUITest_SampleUITests: XCTestCase {
    let app = XCUIApplication()
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
    }

    func testLogin() {
        app.launch()
        let page = LoginPage(app: app)
            .typeEmail("test@example.com")
            .typePassword("123456789")

        XCTAssert(page.goToSuccessPage().exists, "successページに到達した")
    }
}

はまったところ

文字入力のUITestが不安定

  • Simulatarの設定を修正することで改善されます。
    • Simulatarのkeyboardの設定を変更する
    • Simulatar -> I/O -> Keyboard -> Connect Hardware KeyboardをOFFにする

スクリーンショット 2021-03-05 14.27.24.png

UITestの実行が不安定

サンプルコード

感想

  • 発表を聞いた上で実装したことで理解が深まった。
  • PageObjectパターンを採用することで画面に依存せずにUITestが書けるのでメンテしやすい印象を持ちました
  • XcodeのUIテストが不安定な部分があるので対策が必要
  • 既存のプロジェクトだといきなり全部に展開するのは難しいので小さいところから徐々に増やしていきたいです

参考

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2