iOSでswift-snapshot-testingを使う

## swift-snapshot-testingとは

iOSのSnapShotテストといえば[iOSSnapshotTestCase](https://github.com/uber/ios-snapshot-test-case)が有名ですが、[SnapshotTesting](https://github.com/pointfreeco/swift-snapshot-testing)（swift-snapshot testing）というライブラリもあります。

この2つには大きな違いがあり、

| iOSSnapshotTestCase | swift-snapshot-testing |
|:---:|:---:|
| Objective-Cで書かれている | **Swift**で書かれている |
| 対象はView | 対象は**Viewに限らない** |

特に、『対象はViewに限らない』というところがポイントだと思います。
Snapshotの意味からすれば、こちらが普通ともいえますね。

とはいったものの、まずは画面のSnapshotを撮るために導入を検討するのでは？という観点からセットアップについて書いていきます。

**サンプルプロジェクト**
[swift-snapshot-testing-sample](https://github.com/keygx/swift-snapshot-testing-sample)

## インストールする

[README](https://github.com/pointfreeco/swift-snapshot-testing/blob/master/README.md)によると、Carthage / CocoaPods / Swift Package Managerに対応しています。

今回はCarthageでインストールする場合を紹介します。

Carthageインストール時のポイントは

- テストターゲットのみに追加
- 通常行うcopy-frameworksは行わない

になります。

つまり、テスト時のみ必要なライブラリですので、リリースビルドには含めないようにするということですね。

```Cartfile
github "pointfreeco/swift-snapshot-testing" ~> 1.6
```

[README](https://github.com/pointfreeco/swift-snapshot-testing/blob/master/README.md)の通りにセットアップを進めると、テストターゲットのBuild Phasesは、このようになります。
<img width="1423" alt="test_build_phases.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15905/1b36114e-f198-9e25-fb99-028b938a316d.png">


## XCodeを設定する

#### 画像フォルダを設定

Snapshotテストが失敗した際に出力される画像を格納する、フォルダのパスを設定します。
この設定をしなくてもテストは可能なのですが、設定しないとファイルが出力されません。また、このフォルダは **.gitignore** に追加し、リポジトリには含めないようにしておきます。

![edit_scheme2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15905/f103563f-01ee-03be-d1fd-ead41a7f67e3.png)

SchemeのEnviroment Variablesに以下を追加します。

```
[Name] SNAPSHOT_ARTIFACTS

[Value] $(SOURCE_ROOT)/$(PROJECT_NAME)Tests/__Snapshots__/Failure
```


#### シミュレータ起動時の言語を設定

ローカル環境でSnapshotテストが通ったので、意気揚々とPushしたところ、CIでテストがコケるという現象が発生しました。CIから失敗時の画像をダウンロードして確認してみたところ、フォントが微妙に違っていました。
私の開発環境は日本語の設定ですが、CIではOSが素の状態でインストールされた状態なので、システムフォントが異なってしまうのが原因のようです。

シミュレータを日本語で起動することによって、解決できました。

![edit_scheme1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15905/9596b563-39f7-dce7-14b5-7b624f47444b.png)

SchemeのArguments Passed On Launchに以下を追加します。

**-AppleLanguages (ja)**

※アプリにより状況が異なると思いますので、必要に応じて対応してください

## リファレンス画像を出力する

```HomeSnapShotTests.swift
func testHomeViewController() {
    record = false
    
    let storyboard = UIStoryboard(name: "Home", bundle: nil)
    let vc = storyboard.instantiateInitialViewController()!
    
    SnapshotDevices.allCases.forEach {
        assertSnapshot(matching: vc, as: .image(on: $0.config), named: $0.name)
    }
}
```

```swift
record = true
```
にするとリファレンス画像が書き出されます。
また、場所の指定はできないようで、テストクラスと同じディレクトリに書き出されます。
<img width="402" alt="project_dir.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15905/618dc298-1d51-a724-74bd-12be3865c6b3.png">
リファレンス画像の命名規則は、下記のようになります。

```
__Snapshots__/テストクラス名のフォルダ/テストケース名.{named:に渡した名前}.png
```

## テストを実行する

ユニットテストの一環として実行されます。シミュレータの端末を選択し、コマンド＋Uです。

READMEによると “単一のシミュレータからすべてのSnapshot画像を生成する” とのことなのですが、テスト時に選択された端末によって得られる画像が異なるようです。特にノッチのあり／なしで余白的な部分が変わりました。これは、リファレンス用のSnapshot画像を撮った端末とテスト時の端末が異なるとテストが失敗してしまうことを意味しています。
Snapshotテスト導入後は、ユニットテスト時の端末を統一するようにしました。
また、CIでのテストも、同じ端末を指定しておく必要があります。


## 画像のDiff

swift-snapshot-testingはテスト失敗時の差分画像を出力してくれません。
差分を比較したい場合は、[Kaleidoscope](https://www.kaleidoscopeapp.com)を使っています。2枚の画像を並べたり、重ねて違いを表示したりできます。

| Two-Up | Difference |
|:---:|:---:|
| <img width="1152" alt="スクリーンショット 2019-10-06 13.51.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15905/542d0e01-368f-8900-34a7-1ec96e2065b0.png"> | <img width="1152" alt="スクリーンショット 2019-10-06 13.51.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15905/32370bf7-588e-c7c0-c8ea-6ac53e4a6ff7.png"> |

個人的には長年アプリケーションフォルダで眠っていたKaleidoscopeが、活躍する日が来てくれてうれしいです。

## 最後に

この記事のとおり導入はそれほど難しいものではありません。アプリをより良くしていこうとSnapshotテストの導入を検討している方々の参考になれば幸いです。