ios-snapshot-test-caseでスナップショットテスト!

はじめに

今まで私の開発しているアプリではfastlaneのsnapshot機能を使ってスナップショットを撮影していました。差分を検知する機能はないのでもちろん目視で!

fastlaneでsnapshotを取る!前にまとめた記事です。

iosdcでスナップショットテスト実戦投入という発表を聞いて「速い!差分が出る!簡単!」の三拍子が揃っていたので自分のアプリにも導入してみました。

ios-snapshot-test-caseの導入

ios-snapshot-test-case本家のREADMEに手順は書いてありますが一応簡単に解説しておきます。

iOSSnapshotTestCaseのimport

carthageは使ってないのでわかりません。

cocoapodsで設定する時はuse_frameworks!の部分までしっかりかかないとクラッシュするので注意です!

target 'WhoLegTests' do
    use_frameworks!
    pod 'iOSSnapshotTestCase'
  end

画像を吐き出すフォルダの指定

テストを動かすSchemeに設定正解の画像と間違っていた時の画像を吐き出すフォルダを指定します。

本家ではIMAGE_DIFF_DIRの方は入れてなかったのでなくてもいいのかもしれません。

FB_REFERENCE_IMAGE_DIR$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages
IMAGE_DIFF_DIR$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/FailureDiffs

ここまでで導入が終わりです。失敗しているとクラッシュします!

間違えやすい部分としてライブラリのimport部分がつまづきやすいと思うので注意してみてください。私もひっかかりました。

テストを書いてみる!

まずは他の記事でもよく紹介されているViewのテストをやってみます。

1.失敗前提で画像を作る

テストを導入したのはだれのあしというアプリです。よければ使ってください!

比較対象がないのでまずはスクリーンショットを撮ってみます。以下のようにコードを記述してテストを走らせます。

import FBSnapshotTestCase
@testable import WhoLegTests

class SnapShotTests: FBSnapshotTestCase {
    override func setUp() {
        super.setUp()
        recordMode = true //ここがtrueだと画像を出力します。
    }

    func testView() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
        view.backgroundColor = .red
        FBSnapshotVerifyView(view)
    }
}

failed – Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!

こんな感じでエラーが出ると思いますが、Testsフォルダに画像ができているはずです!実機で走らせると画像が出力されません。

※ テストはシミュレータでやること!!!
※ テストはシミュレータでやること!!!
※ テストはシミュレータでやること!!!

2.もう一回テストをする

画像は一度生成してしまえば次からは入らないのでrecordModeをfalseにして実行します。

すると今度はテストが問題なくグリーンになるはずです!

import FBSnapshotTestCase
@testable import WhoLegTests

class SnapShotTests: FBSnapshotTestCase {
    override func setUp() {
        super.setUp()
        recordMode = false //ここを修正!!
    }

    func testView() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
        view.backgroundColor = .red
        FBSnapshotVerifyView(view)
    }
}

3.間違えを試してみる

スクリーンショットが撮れることはわかりましたが、viewの色が違ったりした場合にしっかりレッドが出るかを確認してみます。

import FBSnapshotTestCase
@testable import WhoLegTests

class SnapShotTests: FBSnapshotTestCase {
    override func setUp() {
        super.setUp()
        recordMode = false
    }

    func testView() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
        view.backgroundColor = .green //ここを変更しました!
        FBSnapshotVerifyView(view)
    }
}

このコードを実行するともちろんエラーになります。エラーの内容はこんな感じです。

failed – Snapshot comparison failed: Optional(Error Domain=FBSnapshotTestControllerErrorDomain Code=4 “Images different” UserInfo={NSLocalizedFailureReason=image pixels differed by more than 0.00% from the reference image, FBDiffedImageKey=<UIImage:0x6000020be400 anonymous {60, 60}>, FBReferenceImageKey=<UIImage:0x6000020bcd80 anonymous {60, 60}>, FBCapturedImageKey=<UIImage:0x6000020a8bd0 anonymous {60, 60}>, NSLocalizedDescription=Images different})

プロジェクトフォルダを確認するとFailureDiffsというフォルダが自動生成されていて差分があった画像を出力してくれています。

referenceが正しい画像で、failedがテスト時に表示したもの、diffが差分です。今回は色の差分だったので薄いピンクになっています。

ViewControllerを撮影してみる

基本はViewの時と同じです。@testable import WhoLegでプロジェクトをimportすればViewControllerをTestsの中で使うことができます。

DI周りにライブラリを使っていないのでちょっとみづらいですが、ViewControllerを作ってFBSnapshotVerifyViewで判定してるだけです。

import FBSnapshotTestCase
@testable import WhoLeg

class SnapShotTests: FBSnapshotTestCase {
    override func setUp() {
        super.setUp()
        recordMode = true
    }

    func testInformationScreen() {
        let vc = TitleViewController()
        let presenter = TitlePresenterImpl(model: TitleModelImpl(quizRepository: QuizRepositoryImpl()))
        vc.injector(presenter: presenter)
        FBSnapshotVerifyView(vc.view)
    }
}

画像を出力することができました!NavigationBarが出ていませんが、その辺り各種設定があるようです。

また、私のアプリの場合は真ん中の画像とラベルの値がランダムで切り替わるのですが、それも一部を無視する指定ができるようです。

機会があれば記事にしたいと思います。

参考文献

スナップショットテスト実戦投入 / Practical Snapshot Testing

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