XCUITestを試す #2 (TableView編)


# 概要

前回(2年前)の続きです。久しぶりに時間が取れたので書いていきます。
http://qiita.com/rnishimu22001/items/5be161e0350dfc3aa788

今回はUITableViewのXCUITestについて書いていきます。

サンプルコードはこちらです。
https://github.com/rnishimu22001/UITestApplication/blob/master/UITestApplicationUITests/UITestApplicationTableUITests.swift

# 今回の環境

Xcode: ver 11.1
Swift: ver 5.0

# テスト対象側の準備

## 今回テストするアプリ

セルの中にUILabelを配置し、自身のrowの値を入れただけのセルが1001個並ぶアプリをテストしていきます。
0始まりなので一番最後のセルのrowは1000です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/c1a6b1fd-ff9a-b2a4-2fd6-dfa6d33fd583.png)


## UITableViewのテストで試す項目

* セルがいくつ表示されているか？
* セルに何が表示されているか？
* セルをタップする

などテストターゲットのUITableView内のUITableViewCellを対象にしたテストが多いと思うのでそのあたりを触っていきます。

## UITableViewCellをテストするには？

静的なUIKitのパーツと同じくAccessibility IDをセルに付与して取得していくのが良さそうです。
https://qiita.com/rnishimu22001/items/5be161e0350dfc3aa788#ボタンの要素を取得する

他のViewと同じく、Interface Builderから設定できます。
<img width="710" alt="スクリーンショット 2019-10-14 13.40.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/212a011e-14f7-59c5-5b3d-95766ba16939.png">

# UIテスト

## テスト対象アプリのsetUp

こちらと同様にsetUpのタイミングでアプリをlaunchしておきます。
https://qiita.com/rnishimu22001/items/5be161e0350dfc3aa788#アプリを起動する

```swift
final class UITestApplicationTableUITests: XCTestCase {
    /// setupで起動済みのapplication
    let app = XCUIApplication()
    
    override func setUp() {
        super.setUp()
        self.app.launch()
    }
```

以下、別のコードに出てくる**self.appは上記のコードのlaunchしたXCUIApplication()**として読んでください。

## 表示セルの数の検査

XCUIApplicationのcellのプロパティからAccessibility IDでテスト対象のセルを全て抜き出せます。
https://developer.apple.com/documentation/xctest/xcuielementquery/1500679-matching

この際に画面外にあるまだ未表示のセルも取得できます。

```swift
/// app配下のcellで指定したAccessibility IDに合致するものを全て取得する
let cells = self.app.cells.matching(identifier: "セルに設定したAccessibility ID")
/// 取得できたセルの数の検査
XCTAssertEqual(cells.count, 1001)
```

注意したいのが上記のコードだとUITableView内の**Sectionを跨いで**セルを取得できてしまうため、SectionによってAccessibility IDを変えないと特定Sectionだけのテストはできなそうです。

XCUIElementの検索範囲を「UITableView内の任意のSection内のみを範囲に指定」もできるか調べたがindexPathを指定するAPIが発見できなかったので誰か知ってたら教えてください。 :sob: 

## テスト対象のセルの取得

XCUIElementQueryの`func element(boundBy: Int)`を利用すれば特定のセルにアクセスできます。
https://developer.apple.com/documentation/xctest/xcuielementquery/1500842-element

```swift
/// app配下のcellで指定したAccessibility IDに合致するものを全て取得する
let cells = self.app.cells.matching(identifier: "セルに設定したAccessibility ID")
/// 上から順番にセルが入っているのでindexを指定して特定のセルにアクセス、0始まりに注意
let cell = cells.element(boundBy: 1)
```

また、まだ画面内に表示されていないセルに対してもアクセスできます。

```swift
/// まだ画面内には表示されてないセルのindex
let cellAt1000 = cells.element(boundBy: 1000)
let label = cellAt1000.staticTexts[1000.description].label

// tableViewの1000番目のセルは存在している
XCTAssertTrue(cellAt1000.exists)
// 1000番目のセルは画面内にはいないのでHittableはfalse
XCTAssertFalse(cellAt1000.isHittable)
// rowの値が1000なのでlableの値も1000
XCTAssertEqual(label, "1000")
```

## 指定のセルをタップ

XCUIElementの`func tap()`を使います。
https://developer.apple.com/documentation/xctest/xcuielement/1618666-tap

TableView内にセルが表示されていない場合はセルの位置までスクロールしてくれたので非常に便利。

```swift
//　任意のセルを指定
let cell = cells.element(boundBy: 1000)
// 取得下セルに対してタップ
cell.tap()
```

# 感想

Xocde8で検証したときには下の方にあるセルはスクロールしないと取得できなかった(ハズ)。
あまり実用的でなかったがXCUITestをXcode11で久しぶりに触ってみてパワーアップを実感しました :muscle: