Unit TestよりもUI Testでテストした方が良いことがあります。
Xcode9からXCUIApplication
が強化され、よりUI Testの幅が広がりました。
今回はその中の他のアプリの操作について書きたいと思います。
他のアプリを操作することによって、URLスキームのテストとか、カメラや写真、通知など各パーミッションのテストが可能になります。
- SafariでURLを入力してスキームのテスト
- iOSの設定を操作して、各パーミッションの許可/不許可を変更する
- TIPS
- 操作対象エレメントの取得方法がわからない場合
- 言語設定
- 基本古いXcodeでビルドしている場合
- Apple defaultアプリのBundle IDがわからない
SafariでURLを入力してスキームのテスト
スキーム起動に対応していていて画面やその他の状態によりスキーム実行された時の解釈が異なる場合に全てのケースを網羅的にテストしておきたいことがあります。
Xcode8でも出来なくもなかったのですが、やや複雑で正攻法ではなかったためその分シンプルではありませんでしたし、メンテナンス性に欠ける記述になってしまうのであまりよくありませんでした。
Xcode9からXCUIApplication
をBundle IDからイニシャライズできるようになりました。つまり自分のアプリ以外のアプリを操作できるようになりました。
具体的にはSafariでURL(スキーム)を入力して自分のアプリを呼びたい場合、下記のように書けます。
let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
// Safari起動
safari.activate()
if safari.otherElements["URL"].exists {
safari.otherElements["URL"].tap()
}
// URL入力
let addressBar = safari.textFields["URL"]
addressBar.typeText(url)
safari.buttons["Go"].tap()
// アプリスキーム起動
safari.buttons["Open"].tap()
// アプリの復帰を待つ
let app = XCUIApplication()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 30))
意図通りの画面が表示されているかは以下のような形でXCTAssert
を使用して検証することができます。
XCTAssert(app.navigationBars["意図している画面タイトル"].exists)
iOSの設定を操作して、各パーミッションの許可/不許可を変更する
写真へのアクセスや通知の許可状況を変更してアプリの挙動をテストしたい場合があります。
この場合もSafariと同様にBundle IDからXCUIApplication
を生成、iOSの設定を操作することで可能になります。
// 設定アプリを取得
let settings = XCUIApplication(bundleIdentifier: "com.apple.Preferences")
// 起動
switch settings.state {
case .notRunning:
settings.launch()
case .unknown, .runningBackgroundSuspended, .runningBackground:
settings.activate()
// ルート階層に戻す
var backButton: XCUIElement
repeat {
backButton = settings.navigationBars.buttons.element(boundBy: 0)
if backButton.exists {
backButton.tap()
}
} while backButton.exists
case .runningForeground:
// Nothing to do
break
}
// ルート階層から辿る
let tablesQuery = settings.tables
tablesQuery.staticTexts["Privacy"].tap()
tablesQuery.staticTexts["Photos"].tap()
if tablesQuery.staticTexts["自分のアプリ名"].exists {
tablesQuery.staticTexts["自分のアプリ名"].tap()
if authorized {
// 許可する
tablesQuery.staticTexts["Read and Write"].tap()
} else {
// 不許可にする
tablesQuery.staticTexts["Never"].tap()
}
}
// 自分のアプリを起動する
let app = XCUIApplication()
app.launch()
TIPS
操作対象エレメントの取得方法がわからない場合
UI Testでは実際の操作をそのままコード化できる仕組みが用意されています。
以前のXcodeではJavaScriptのコードに落とし込むような感じだったと思いますが進化し、確かにこちらの方が直接的ですね。
そのままだと異常にネストが深いエレメント取得になっている場合があるので、
よりシンプルでわかりやすいコードに変える必要があります。
UI Test実行して、ブレークポイントを貼れば停止するので、その時点で何がどのような形で取得できるのか、Debug Console上で調べるとより簡潔に取得できるコードがわかります。
言語設定
言語設定が英語ではない場合、サンプルで示したコードではうまく通りません。tablesQuery.staticTexts["Privacy"].tap()
などで取得できずにfail扱いになります。
下記のように分岐しておけば、日本語環境でも実行できますが、もっとスマートな方法がないものか模索中です。
if tablesQuery.staticTexts["Privacy"].exists {
tablesQuery.staticTexts["Privacy"].tap()
} else if tablesQuery.staticTexts["プライバシー"].exists {
tablesQuery.staticTexts["プライバシー"].tap()
} else {
XCTFail()
}
基本古いXcodeでビルドしている場合
テストコードに以下のような分岐を設けておけば、Xcode8以前の環境では実行されないため、エラーになりません。
#if swift(>=3.2)
// Xcode9以降でのみ実行したいコード
#endif
Apple defaultアプリのBundle IDがわからない
ググると以下のようなサイトが出てきます。
命名規則が結構ガバガバなんですね