Xcode7からのUI Testing&XCUIElementの基本操作まとめ

おはようございます！

セプテーニ・オリジナルの寺坂です！

**[GANMA!](http://ganma.jp)**という無料マンガの配信サービスに携わっています。

GANMA!のiOSアプリではSwiftを使って開発をしているせいで、Swiftネタが多くなってますね。


かく言う今回も、**Swift + UI Testing**について書きます。


<!-- more -->

***


さて。

Xcode7が公開されてからしばらく経ちましたが、

UIRecordingが追加されたことで、よりテストがしやすくなりました。


AndroidのEspressoだとViewのオブジェクトを取得するのが少し大変だったりしますが、

UIRecordingを利用すると、レコーディングボタンを押してシミュレーターをぽちぽち操作するだけなので、

UI Textingのぐっと敷居が下がったような気がします。

ただ、敷居が下がったとはいえ、レコーディングだけでテストできるわけではないので、

今回は**UITestの基本的な書き方とViewの取得・操作方法**について書こうと思います。


## 基本的なテストの流れ

通常は下記の流れでテストを書いていくことになると思います。


1. 起動しているアプリケーション取得
2. View(XCUIElement)を取得
3. Viewに対してテストしたい動作を命令
4. 動作結果を検証

### 1. アプリケーション(XCUIElement)の取得

起動中のアプリケーションを取得するのは、下記の１行で済みます。

```swift
let app = XCUIApplication()
```
### 2. エレメント(XCUIElement)取得

エレメントの取得方法はいくつかありますが、

基本的に、UIViewのaccessibilityIdentifierにセットしたIdentifierを指定して取得します。

##### 取得方法
```swift
let button = app.buttons["identifier"]
```

##### Identifierをセットする方法
```swift
// Storyboardから設定 または コードから設定

// コードからセットする例。ViewController内で。
button.accessibilityIdentifier = "Identifier"
```

### 3. 動作を命令

用意されているメソッドを呼びます。

```swift
// タップ
button.tap()
```

### 4. 動作結果を検証

Assertでチェックします。

```swift
XCTAssertTrue("hogehgoe" == "hogehoge")
```


## XCUIElementの取得・操作方法一覧

テストをする上で、まずはエレメントを取得して操作を実行できなければテストもできません。

UIRecordingを利用すれば割と簡単に取得できますが、

応用したり独自に操作しようとすると、やはり知識が必要になってきます。

下記に、よく使われるXCUIElementの取得・操作方法を羅列していきます。
(取得方法はいろいろあるが、代表的なものを)



### UILabel
```swift
// get
let label = app.staticTexts["identifier"]
```

### UIButton
```swift
// get
let button = app.buttons["identifier"]
// tap
button.tap()
// long tap
button.pressForDuration(10)
```

### UITextField
```swift
// get
let textField = app.textField["identifier"]
// forcus on
textField.tap()
// input text
textField.typeText("テキスト")
```

### UIImageView
```swift
// get
let image = app.images["identifier"]
```

### UITableViewCell
```swift
// get from title
let cell = app.tables.cells.staticTexts["title"]
// get from index
let cell = app.tables.cells.elementBoundByIndex(4)
// tap cell
cell.tap()
```

### UIAlertController
```swift
// get
let alert = app.alerts["タイトル"]
// check showing message
alert.staticTexts["メッセージ"].exists
// tap button 取り方いろいろ
alert.buttons["ButtonTitle"].tap()
alert.collectionViews.buttons["ButtonTitle"].tap()
alert.collectionViews.cells.buttons["ButtonTitle"].tap()
```

### UISlider
```swift
// get
let slider = app.sliders["identifier"]
// change slider
app.sliders.element.adjustToNormalizedSliderPosition(0)
app.sliders.element.adjustToNormalizedSliderPosition(0.5)
app.sliders.element.adjustToNormalizedSliderPosition(1)
```

### UIPicker操作
```swift
// wheel picker to value
app.pickerWheels.element.adjustToPickerWheelValue("Hogehoge")
```

### UIWebViewリンクタップ
```swift
// tap
app.links["リンク"].tap()
```

### UITabBarのボタン
```swift
// get
app.tabBars.elementAtIndex(0)
```

### UIToolBarのボタン
```swift
// get
app.toolbars.elementAtIndex(0)
```


## 端末操作

### デバイスのホームボタン操作
```swift
XCUIDevice.sharedDevice().pressButton(XCUIDeviceButton.Home)
```

### システムアラート操作
```swift
// アラート時の動作をあらかじめ埋めておく
addUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in
    alert.buttons["Allow"].tap() // 出た時にどうするか
    return true
}
app.buttons["Identifier"].tap() // 位置情報取得ボタン
app.tap() // システムのダイアログ選択実行
```


## キーボードキーの操作

キーボードが開いている状態で。

### キーボードのEnterキー
```swift
// click Enter
app.buttons["Return"].tap()
```

### Deleteキー
```swift
// click Delete
app.keys["delete"].tap()
```

### Shiftキー
```swift
// click Shift
app.keys["shift"].tap()
```

### キーボードの切り替えボタン
```swift
// click change keyboard
app.buttons["Next keyboard"].tap()
```

### アルファベットと数字切り替え
```swift
// alphabets
app.keys["more, letters"].tap()
// numbers
app.keys["more, numbers"].tap()
```


### 予測変換候補を選択
```swift
app.otherElements["変換内容"].tap()
```


## その他の操作

### XCUIElementの存在確認
```swift
XCTAssertTrue(エレメント.exists)
```

### XCUIElementが表示されるまで待つ(非同期処理を挟んだ場合とかに便利)
```swift
let element = app.buttons["Identifier"]
let existsPredicate = NSPredicate(format: "exists == true")
expectationForPredicate(existsPredicate, evaluatedWithObject: element, handler: nil)

// expectationForPredicateが完了するまで待つ
waitForExpectationsWithTimeout(10, handler: nil) // handlerにはエラー時のハンドリングを書く
```


## 参考URL

元記事 - http://labs.septeni.co.jp/entry/2015/11/27/092534

WWDC https://developer.apple.com/videos/play/wwdc2015-406/

iOS9 Day-by-Day :: Day 2 :: UI Testing https://www.shinobicontrols.com/blog/ios9-day-by-day-day2-ui-testing


### GANMA!
* iOS - https://itunes.apple.com/jp/app/zenbu-wu-liao-man-hua-ganma!/id709003148?l=en&mt=8
* Android - https://play.google.com/store/apps/details?id=com.COMICSMART.GANMA
* ブラウザ - http://ganma.jp/