[iOS]ユニットテストでUIのテストをするためのテクニック集

# はじめに

ユニットテスト環境でUI部分のテストをするためのテクニック集です。

# SUT (UIView, UIViewController)の設定方法

## UIViewをそのまま生成

```swift
let view = UIView()
```

## windowのルートビューコントローラーに設定
シミュレーターに画面が出る。実行しているシミュレータの端末に関わらず、画面サイズを変更可能。

```swift
let window = UIWindow(frame:CGRect(origin: .zero, size: CGSize(witdh: 375, height: 667)) // 画面サイズを指定できる　例 4インチ：CGSize(witdh:320, height: 568)
window.rootViewController = hogeViewController
window.makeKeyAndVisible()
```

## 画面のサイズを帰る。
シミュレーターに画面が出る。

```swift
let window = UIWindow(frame:CGRect(origin: .zero, size: CGSize(witdh: 375, height: 667)) // 画面サイズを指定できる

```


## 画面の処理を進ませる。
Runloopでイベントループを回す。

```swift
WIP
```

# UIイベントの発火させかた。

## イベントハンドラーを直呼び
実装コストは低いがアクセス権限を壊す必要がある。

```swift
hogeView.hogeButtonTapped()
XCTAssertEqual(......
```

## コードによるUI操作＋デレゲートメソッド直呼び

```swift

collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .left)
// プログラムアブルな操作はデレゲードメソッドが呼ばれないので直呼び
collectionView.delegate?.collectionView!(collectionView, didSelectItemAt: indexPath)

XCTAssertEqual(......

```

## sendActionsメソッドを使う。

xibの設定が正常に行っているかまでテストできる。

```swift

button.sendActions(for: .touchUpInside)
XCTAssertTrue(....)

```

## リフレクション:perform系メソッドを呼ぶ

プライベートスコープでも呼べる。ただし、メソッド名を間違えるとテスト実行時にエラーが発生する。

```swift

 viewController.perform(NSSelectorFromString("hogeButtonTapped"))

 XCTAssertEqual(......

```

# 検証方法

## プロパティを外だし。
実装コストは低いがアクセス権限を壊す必要がある。なるだけgetterのみ出す。Objectiv-Cはゴニョゴニョするとテスト環境のみ外だしできる。

```swift
XCTAssertEqual(cell.commentLabel.text, "hope")
```

## 画面のスナップショットを取る。
テストが壊れなければ楽。そう壊れなければ

```swift
FBSnapshotVerifyView(vc.view)
```

## Accessibility IdentifierでSubviewを外だし。
プライベートスコープでも呼べる。ただし、失敗すると実行エラーが発生。xibから設定する場合なぜか中々反映されない。コードから反映した方が良い？

```swift
let cell = vc.view.viewMatchingAccessibilityIdentifier("タイトル") as! HogeTableViewCell
XCTAssertEqual(cell.titleLabel.text, "hope")
```

## JSONベースでマッチング
内部の状態をJSON文字列で出力して検証する。返って手間がかかる？

```swift
WIP
```

## ダミーのNavigationControllerを作成して画面遷移の挙動を検証する。
表示させるとテストの実行時間が伸びるので寸止めさせる。

```swift

// テストスパイ化したUINavigationController

final class TestSpyableNavigationController: UINavigationController {

    private(set) var pushedViewController: UIViewController?
    
    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        // コンストラクター呼び出し時とpush時の２回通るため、その対応。
        if viewControllers.count == 0 {
            super.pushViewController(viewController, animated: animated)
        } else {
            pushedViewController = viewController
        }
    }

}

// テストコード

...
viewController.perform(NSSelectorFromString("hogeButtonTapped"))
XCTAssertTrue(nc.pushedViewController!.isKind(of: HogeViewController.self))

```
# まとめ（になっていない)
- 参考文献1にあるようにUI丸ごとテストする場合は、テスト駆動開発というよりかはデバック駆動開発になる。
- 実行時エラーが発生しやすい手法しか取れない点でViewModelを使うよりかは効率が落ちる。とはいえ、その分設計コストは小さいのでトントン？？。
- テスト駆動でプロパティ検証->デバック駆動でスナップショットという流れでやるとそれなりの効率が上がるかも。
- ROIに応じて適切な手法を採用することが肝要。
- Android Test FrameworkみたいにUIを単体テスト、統合テストの両方を切り替えるだけで行える機構が今後でてくるかも。
 
# 参考文献/スライド

(1) [App Architecture: iOS Application Design Patterns in Swift] (https://www.amazon.co.jp/dp/B07D21KRJN)
(2) [iOS Code Testing: Test-Driven Development and Behavior-Driven Development with Swift (English Edition)](https://www.amazon.co.jp/dp/B073LPHJ3G)
(3) [さわって学べる！iOSテスト駆動開発](https://booth.pm/en/items/1040471)
(4)[Viewのテストどうしてますか?] (https://speakerdeck.com/kariad/viewfalsetesutodousitemasuka)
(5) [Snapshot Testing in iOS] (https://speakerdeck.com/susieyy/snapshot-testing-in-ios)