Posted at

XCTest入門 (Swift) ~UITest編~ Part2

More than 3 years have passed since last update.


はじめに

UITest編Part2です。

前回は、UIRecordingを使ってシミュレータでの操作からテストコードを生成しました。

XCTest入門 (Swift) ~UITest編~

前回、UIRecordingを使って操作を記録しましたが

生成されたテストコードが期待通りの動作をしてくれず修正が必要でした。

そのため、今回はコードの書き方を中心にまとめたいと思います。

(前回使用したサンプルプロジェクトをそのまま流用しています。)


テストコードからUIを呼び出す

UIを指定する方法はいくつかありますが、私が実際に使用したものを記載します。


方法1. StoryboardでAccessbilityのIdentifierを設定して呼び出す

スクリーンショット 2016-08-23 0.55.15.png

本サンプルでは、Loginボタンに対して"loginButton"というIdentifierを設定しました。

テストコードで、

XCUIApplication().buttons["loginButton"]

というように指定することができます。

XCUIApplication().buttons["loginButton"].tap()

とすれば、ログインボタンをタップする動作を表現できますね。


方法2. 同じUIが複数存在しない場合はelementのみで呼び出す

表示されている画面に同じUIが複数存在しなければ、elementと書くだけで呼び出すことができます。

上のログイン画面はボタンが1つしか存在しないので、以下のように書くとログインボタンが呼び出されます。

XCUIApplication().buttons.element

ボタンではなく、テキストフィールドであれば

XCUIApplication().textFields.element

という感じです。


方法3. titleを指定する

Identifierでは無く、表示されるタイトルでもUIを指定することができます。

書き方は、Identifierを指定する場合と同様です。

XCUIApplication().buttons["Login"]

私は、

方法1(Identifier指定)と

方法2(同UIが複数存在しない場合のelementでの呼び出し)

を主に使用しています。


操作

操作についても私が実際に使用したものを記載します。

今後、新しく使用したら追記していきたいです。


タップ

xxx.tap()でタップを表現できます。

XCUIApplication().buttons["Done"].tap()

XCUIApplication().textFields["loginTextField"].tap()


ロングタップ

xxx.pressForDuration(<秒数>)

XCUIApplication().textFields["loginTextField"].pressForDuration(1.0)


キーボードで入力

TextFieldなどに対して、xxx.typeText(<入力文字列>)

XCUIApplication().textFields["loginTextField"].typeText("abc")


Pasteboardに文字列をセットしてペースト

事前にペーストボードに文字列をセットしておいて、

"Paste"をタップしてTextFieldにペーストする方法です。

Simulator Screen Shot 2016.08.23 1.33.50.png

UIPasteboard.generalPasteboard().string = "hoge"

XCUIApplication().textFields["loginTextField"].pressForDuration(1.0)
XCUIApplication().menuItems["Paste"].tap()


サンプルコード

前回の記事で、アラートが表示されるケースと画面遷移するケースを書き直してみました。

import XCTest

class SwiftUITestSampleUITests: XCTestCase {

override func setUp() {
super.setUp()

// test Failure時にすぐ止める (trueにすると、test失敗しても最後まで処理する)
continueAfterFailure = false

// セットアップ毎にアプリを起動
XCUIApplication().launch()

// 画面の向きなどを指定したい場合はここで設定しておく
}

override func tearDown() {
super.tearDown()

XCUIApplication().terminate()
}

func testLoginFailedCaseMistype() {
let app = XCUIApplication()
let loginTextField = app.textFields["loginTextField"]

loginTextField.tap()

// ペーストボードに文字列"typo"をセット
UIPasteboard.generalPasteboard().string = "typo"

// テキストフィールドをロングタップ
loginTextField.pressForDuration(1.0)
app.menuItems["Paste"].tap()
app.buttons["Done"].tap()

app.buttons.element.tap()
app.alerts.element.collectionViews.buttons["OK"].tap()
}

func testLoginFailedCaseNotInput() {

let app = XCUIApplication()

// ボタンタップをすぐにできなかった -> sleepしてからタップ
sleep(2) //dispatch_afterでもできた

app.buttons.element.tap()
app.alerts.element.collectionViews.buttons["OK"].tap();
}

func testLoginSuccessful() {
let app = XCUIApplication()
let loginTextField = app.textFields["loginTextField"]

loginTextField.tap()
UIPasteboard.generalPasteboard().string = "hoge"
loginTextField.pressForDuration(1.0)
app.menuItems["Paste"].tap()

app.buttons["Done"].tap()
app.buttons["Login"].tap()

app.buttons["back"].tap()

loginTextField.tap()
app.typeText("Misson complete!")
app.buttons["Done"].tap()
}
}


課題

sleepやdispatch_afterなどで遅延させないとうまくいかない箇所がありました。

どのような場合に遅延処理が必要なのかまだイマイチわかっていません。


さいごに

UITestのテストコードを書いてみてわかったことを整理しました。

UITest使いこなせれば便利そうだけれど、期待通りに動かすのが難しいです。