XCTestでUITestするとき便利なもの、困ったこととその解決策

iOS10のリリースが間近に迫り、いろいろと新しいことにチャレンジする機会が増えそうです。iOS10がリリースされると、以下のような嬉しいことがあります。

- **iOS9以上でしか使用できないXCTestの活躍の場が増える**

というわけで、ここ最近UITestのコードばっかり書いてたので情報整理したい


# XCTestでUITestするやり方
- [公式ドキュメント - User Interface Testing](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/09-ui_testing.html#//apple_ref/doc/uid/TP40014132-CH13-SW1) 
- UITestする時、view（`XCUIElement`）を特定する方法は2つ

```swift:1.accessibilityIdentifierを指定して一意にする
self.hogeButton.accessibilityIdentifier = "hogeButton";
```

```Swift:2.viewのインデックスや、表示されている文字列から特定する
let tablesQuery = XCUIApplication().tables
tablesQuery.cells.staticTexts["ほげほげ"].tap()
tablesQuery.cells.elementBoundByIndex(0).tap()
```
1.が推奨だけど、テストしたい画面のviewにいちいち指定するのが大変。

また、UIRecordingで作られるのは主に2.のようなコード。

なので、基本的に2.でテストコードを書いてみて、viewを特定するのが難しかったら、1.でやってみることにした。

# UITestするとき便利なもの
## UIRecording
 - 割愛、下記を参照
     - http://qiita.com/IsaoTakahashi/items/f9c3ef367d7e0f60e2d6

## Gather Code Coverage をオンにする
- オンにすると、Test Reportsが生成される
- Test Reportsの目玉マークを押すと、テスト中のスナップショットが閲覧できる
- テストに失敗したときどの処理で失敗したのか、とても見やすく見つけやすい
- テストを重ねると、スクショが溜まって、ディスク容量がいっぱいになるので注意
- やり方は下記のスライドの32ページからが１番わかりやすい
 - http://www.slideshare.net/nowsprinting/20150711-ui-testinginxcode7

## appium の inspector 
 - これがあると、実機でテストしたい画面を表示させて、viewの要素を階層ごとに把握できる
 - セットアップや使い方は、下記の動画を見るのが１番楽だった
  - https://www.youtube.com/watch?v=meU4TzI3KNM
 - 一つ注意なのは、実機で inspector を表示するには下記が必要
  - `brew install ideviceinstaller` 　

## Xcode の Debug View Hierarchy
 - [公式ドキュメント - Examining the View Hierarchy] (https://developer.apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html) 
 - Debug View Hierarchyで表示できない画面はviewが多すぎて、viewの特定に時間がかかり、テストに失敗することが多い（たまに成功する）
 - **Debug View Hierarchyで表示されない画面のUIテストは失敗率が高い（`accessibilityIdentifier`を指定しても）**

## 先人の知恵
- http://qiita.com/i_terasaka/items/2cf7d0f7146f32c3f2c1
- http://qiita.com/shirochan/items/10271912289dc563cc36
- http://masilotti.com/ui-testing-cheat-sheet/

# UITestするときに困ったことと、その解決策
## cell や button をタップしてくれない時がある
- `Failed to scroll to visible (by AX action) ・・・`の場合
 - http://stackoverflow.com/questions/33422681/xcode-ui-test-ui-testing-failure-failed-to-scroll-to-visible-by-ax-action

- `Failure fetching attributes for element ・・・` の場合
 - Xcodeのデバックエリアで `po XCUIApplication().tables.cells.elementBoundByIndex(0).buttons["ほげほげ"]`とかすれば、対象のviewは一応特定できるがテストを実行すると失敗することが多い（たまーに成功するから厄介）
 - **このエラーが出る画面では Debug View Hierarchy でおそらく表示できないので、viewが多すぎて `XCUIElementQuery` で探せないのが原因なのだと思う**
 - 画面のUIを考え直すか、テストを諦めるか・・・
 
## タップやスワイプなどのイベント動作が遅い
- `Failed to get refreshed snapshot`というエラーが出る
- viewが多すぎるのが原因だけど、`UIPicker`の`numberOfRowsInComponent`で返す値が大きすぎると出る
 - http://stackoverflow.com/questions/35977575/failed-to-get-refreshed-snapshot-error-when-ui-testing-with-a-uipickerview 

## TextView や TextField にうまく文字が入力出来ない時
- `keyboardType` に `UIKeyboardTypeEmailAddress` を指定しているとき、日本語だとたまにキーパットではなく、フリック入力のキーボードが立ち上がることがある
- これだと`test@test.com`みたいなアドレスを入れたい時、`てst@てst.com`みたいになるので困る
- ダブルタップしてペーストすれば入力自体は可能
 - http://stackoverflow.com/questions/32712036/xcode-ui-testing-typing-text-with-typetext-method-and-autocorrection

## 非同期処理などでviewが表示されるのを待ちたい
- 広告表示、APIの取得 etc で表示が遅れるとき
- `waitForExpectationsWithTimeout`を使う
 - http://masilotti.com/xctest-helpers/

## NSTimerなどのテストで遅延したい時
- ストップウオッチがある画面などのUIをテストする時、一定の時間待ってからボタンをタップとかしたい
- `waitForExpectationsWithTimeout`と`dispatch_after`の組み合わせでやる
- 例えば、開始ボタンを押すと、開始ボタンが一時停止ボタンになり、一時停止を押すと再開ボタンに変わる、みたいなストップウオッチ画面の場合

ストップウオッチのボタンの移り変わり | 
:----------------:| 
<img src="https://qiita-image-store.s3.amazonaws.com/0/37385/f311e868-a119-0bfc-0740-05e8c689cce2.png" width="900"/>  | 



```Swift:例「ストップウオッチのテストのために60秒遅延させる」

・・・

let app = XCUIApplication()
app.buttons["開始"].tap()
        
// ストップウオッチのテストのために60秒遅延
let timeout:Double = 60
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * Double(NSEC_PER_SEC)))
dispatch_after( dispatchTime, dispatch_get_main_queue(), {

    app.buttons["一時停止"].tap()
    app.buttons["完了"].tap()
});
        
// 再開ボタンが表示されるまで（一時停止が押されるまで）待って、後続の処理を遅延
let existsPredicate = NSPredicate(format: "exists == 1")
expectationForPredicate(existsPredicate, evaluatedWithObject: app.buttons["再開"], handler: nil)
waitForExpectationsWithTimeout(timeout + 3, handler: nil)

・・・

```

# XCTestでUITestを書いてみて
- 複雑な画面でなければ失敗することは少ない感じ
- Debug View Hierarchyで表示できない画面は、UIを考えなおす必要がありそう
- 初期コストはとても高いですが、一度書いてしまえば後がとても楽**（それがテストというもの）**
- EarlGreyなどのOSSを使うと、人によっては初期コストが少し下がるかもしれません
- appiumなら、クライアントもサーバーもテストが全てRubyとかJavaにできるので、お好みで