iOSSnapshotTestCaseを使ったユニットテスト

最近テスト書かなきゃという謎の焦燥感が高まっております🔥
そんな折iOSSnapshotTestCaseなるものの存在を知ったので使い方をまとめます✊

# iOSSnapshotTestCaseとは
https://github.com/uber/ios-snapshot-test-case
テスト対象の`UIView`または`CALayer`のスナップショットを撮り、あらかじめ用意しておいた参考画像と比較し、一致するか確認するテスティングフレームワークです。
参考画像を生成することもできるので、必ずしも事前に参考画像を用意する必要はありません。
これを使うことで、いろんなパターンのビューの画像を生成したり、リグレッションテストを実施することができます。

# 使い方
https://github.com/uber/ios-snapshot-test-case#installation-with-cocoapods

## インストール
CocoaPodsでインストールできます。
テストのターゲットに`iOSSnapshotTestCase`を追加します。

```rb:例えばこんな風に
target "プロジェクトTests" do
  use_frameworks!
  pod 'iOSSnapshotTestCase'
end
```

## 画像の置き場を設定

比較あるいは生成した参考画像、テストが失敗したときの画像を置く場所を設定します。
いつくか方法があるみたいですが、READMEに載っているやり方だとSchemeの環境変数に以下を定義してやります。
もしディレクトリのパスやターゲットの名前を変更している場合は、適宜合わせてください。
ディレクトリが存在しない場合は勝手に生成してくれるので、事前に作っておく必要はありません。

|name|value|
|:-:|:-:|
|`FB_REFERENCE_IMAGE_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages`|
|`IMAGE_DIFF_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/FailureDiffs`|

Edit Schemeを選んで、
![スクリーンショット 2018-09-26 22.07.32.png](https://qiita-image-store.s3.amazonaws.com/0/84917/cc393bed-8b34-59a0-576d-68dd09690553.png)

RunのArgumentsにあるEnvironment Variablesに書きます。
![スクリーンショット 2018-09-26 22.08.56.png](https://qiita-image-store.s3.amazonaws.com/0/84917/fbed18fc-bc6c-0d75-ebf6-b551fd2c4beb.png)

Testじゃなくていいの？と思われるかもしれませんが、デフォルトでRunの設定を引き継ぐようになっているようです。もちろんチェックを外してTestだけに書いてもらっても構わないと思います。
![スクリーンショット 2018-09-26 22.09.08.png](https://qiita-image-store.s3.amazonaws.com/0/84917/7ec8fd41-9fdf-5fb1-eb6c-e75dfa66d110.png)

## テスト記述

### クラス定義
`FBSnapshotTestCase`を継承したクラスを定義します。`FBSnapshotTestCase`は`XCTestCase`のサブクラスです。

```swift:テストファイル上の方
import XCTest
import FBSnapshotTestCase
@testable import SwiftTest // 適宜プロジェクトに合わせて置き換える

class SomeViewTests: FBSnapshotTestCase {
...
```

### setup
`setUp()`で`recordMode`の設定をします。参考画像を生成したい場合は`true`、既にある参考画像と比較を行う場合は`false`を入れます。

```swift:setUp
...
class SomeViewTests: FBSnapshotTestCase {

    override func setUp() {
        super.setUp()
        self.recordMode = false // or true
    }
...
```

### テストケース
`XCTestCase`と同様に`test`で始まるメソッドを書いていきます。画像の比較または参考画像の生成には`FBSnapshotVerifyView()`を使います。

```swift:例えばビューが赤い/青いことのテスト
...
    func testRed() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 60))
        view.backgroundColor = .red // テストしたい状態にする
        FBSnapshotVerifyView(view)  // 比較 or 参考画像生成
    }

    func testBlue() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 60))
        view.backgroundColor = .blue // テストしたい状態にする
        FBSnapshotVerifyView(view)   // 比較 or 参考画像生成
    }
...
```

# 使用上の注意

## シミュレータでないとテストできない
実機を選んだ状態でテストすると、参考画像が見つからない的なエラーでfailします。
テスト実行時はシミュレータを選択した状態にしておきましょう。

## サイズが(0, 0)のビューは画像が生成されない
考えてみれば当たり前ですが、`recordMode`を`true`にして参考画像を生成する際に、ビューのサイズが(0, 0)だと画像が生成されません。ただし、テストfailのメッセージには画像を生成した旨が表示されます。
参考画像が格納されるディレクトリも生成されないので、一番最初に実行する際には注意が必要です。

# 使ってみて思ったこと

## 動作確認にとても良い
従来はアプリを起動して目的の画面に行って目的のビューを見る、ということをデータのパターン分繰り返していました。それがiOSSnapshotTestCaseを使えば、必要なデータだけ用意して`recordMode = true`でテストを実行すれば画像を生成してくれるので、複数パターンあるビューや複数端末サイズでの動作確認がとても楽になりました。

## テストの知識がそんなにいらないので楽
モックを作って置き換えてみたいなことは一切せず、ビュー作るだけでいいので、テストに不慣れな人でもすぐ取り組むことができます。

## 画像の容量が際限なく増えていくのが若干不安
いろんなパターンでビューの画像を取りまくるので、容量もどんどん増えていきます。CIで`git clone`するときに遅くなってしまわないか不安はあります。

# おわりに
テストというと難しいもの、というイメージがどうしてもありますが、iOSSnapshotTestCaseを使ったテストは非常に簡単です:tada:
これを足がかりにテストを書きはじめて、iOSSnapshotTestCaseでカバーできない部分があることを知り、モックなどを使う次のステップへ、という風な流れを作りやすいんじゃないでしょうか:smile_cat:
どんどんテスト書いていきましょう！:punch::punch::punch: