今こそgoogle/EarlGreyを使って自動UIテストに入門しよう

今年の2月にgoogleがiOS用自動UIフレームワーク[EaryGrey](https://github.com/google/EarlGrey)を公開してからおよそ10ヶ月が経ちました。EarlGreyは今現在も成長途中であるOSSではありますが、つい先日バージョン[1.6.0](https://github.com/google/EarlGrey/releases/tag/1.6.0)がリリースされ、従来と比べて断然に導入が簡単になりました。
具体的には、以前のバージョンでは手動で各種Xcodeプロジェクト設定を行う必要があったのに対し、現在のバージョンでは各種ツールにより半自動で導入できるように改善されています。また、Swift 3にも正式対応[^1]しました。

今回は、この最新版のEarlGreyの導入方法と簡単なUI自動テストの書き方について紹介したいと思います。

環境
- Xcode 8.2
- Swift 3.0.2
- iOS 10.2

# 事前準備
## テストターゲットの追加とSchemeの設定
**EarlGreyはUnitTestsターゲットを対象**としてUI自動テストを実行します(**UITestsターゲットではない点に注意**)。
事前準備として、最初に「Editor -> Add Targets」と辿り、「iOS Unit Testing Bundle」を選択してユニットテストターゲットを追加しておきます。
次に「Product -> Scheme -> Manage Scheme」と辿り、「+」ボタンからユニットテストターゲット用Shcemeを追加して、「Shared」オプションにチェックを入れて下さい（もちろん、ユニットテストターゲットの追加については、プロジェクト新規作成時に「Inclue Unit Tests」にチェックを入れることで実現しても良いです)。
以下、EarlGreySwiftSampleAppがアプリターゲット、EarlGreySwiftSampleAppTestsがユニットテストターゲットとして話を進めます。

![Qiita_EarlGreyAddUnitTestsTarget.png](https://qiita-image-store.s3.amazonaws.com/0/16102/88335b1b-8b60-e536-a68b-2b9dfe8fda69.png "Qiita_EarlGreyAddUnitTestsTarget.png")

![Qiita_EarlGreyUnitTestsScheme.png](https://qiita-image-store.s3.amazonaws.com/0/16102/a0178eef-5862-2ab2-fdf3-5071b26f9a09.png "Qiita_EarlGreyUnitTestsScheme.png")

## earlgrey gemのインストール
現在のEarlGreyはRubyGems化されたearlgrey gemと後で述べるCarthageを組みわせて使用することにより、半自動的にXcodeに導入できるようになっています。まずは下記の通り、[Bundler](https://github.com/bundler/bundler)を使ってearlgrey gemをインストールしておきます。

```rb:Gemfile
source "https://rubygems.org"
gem "earlgrey"
```

```bash:Bundlerを使ってインストール
$ bundle install --path=vendor/bundle --binstubs=vendor/bin
```

# XcodeプロジェクトへのEarlGreyの導入
Carhageと先ほどのearlgrey gemのコマンドを使って導入します。はじめにCarthageを実行します。

```Cartfile.private
github "google/EarlGrey" "1.6.0"
```
```bash:Carhtageコマンドの実行
$ carthage update EarlGrey --platform ios
```

**対象テストコードがObjective-Cベースである場合、**ここまででEarlGreyの導入は完了です。

**対象テストコードがSwiftベースである場合は、**さらに以下を実行します。

```bash:earlgreyコマンドの実行
$ bundle exec earlgrey install --target=EarlGreySwiftSampleAppTests --swift_version=3.0
```

「target」オプションには対象となるユニットテストターゲット、「swift_version」オプションには使用するSwiftのバージョンを指定します。Swift 3系(3.0, 3.01, 3.0.2)を使用する際は`3.0`、Swift 2.3を使用する際は`2.3`を指定します。
EarlGreyは基本部分がObjective-Cで構成されており、このgemコマンドを実行することによりSwiftで必要となるラッパー用のファイル`EarlGrey.swift`が追加されます。


# テスト対象アプリの作成
ここでは、0以上の整数が入力されることを期待した`age`という`TextField`、およびそのことを確認するための`confirmAge`という`UIButton`を持ったサンプルアプリを対象とします。
※以下、EarlGreyの使い方を簡単に理解してもらうために、UI的に微妙だったり、冗長なコードだったりしていますが、ご容赦ください:-)

![Qiita_EarlGreyStoryboard_640x480.png](https://qiita-image-store.s3.amazonaws.com/0/16102/c64ec957-4e58-8dc8-f9da-431aee00d423.png "Qiita_EarlGreyStoryboard_640x480.png")


アプリターゲット`EarlGreySwiftSampleApp`の`ViewController.swift`にコードを追加します。コード中(1)でコメントしているように、後のEarlGreyテストコードでUIを特定するために`accessibilityIdentifier`を指定する必要があります。なお、この値については、Storyboardを表示させた状態で(Interface Builderから)、`Identitiy Inspector`から設定してもOKです。

```swift:ViewController.swift(EarlGreySwiftSampleAppターゲット)
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var age: UITextField!
    @IBOutlet weak var confirmAge: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // (1) Needs to set accessibilityIdentifier for EarlGrey tests.
        self.age.accessibilityIdentifier = "ageTextField"
        self.confirmAge.accessibilityIdentifier = "confirmAgeButton"
    }

    @IBAction func confirmAge(_ sender: Any) {        
        if let age = Int(age.text!), age >= 0 {
            let alertController = UIAlertController(title: "Confirmed", message: "The age that you input is \(age).", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .default) { _ in })
            self.present(alertController, animated: true, completion: nil)
        } else {
            let alertController = UIAlertController(title: "Error", message: "Input an integer greater than or equal to 0.", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .default) { _ in })
            self.present(alertController, animated: true, completion: nil)
        }
    }
    
}
```

# テストコードの追加
ユニットテストターゲット`EarlGreySwiftSampleAppTests`の`EarlGreySwiftSampleAppTests.swift`にコードを追加します。`grey_accessibilityID`によって`accessibilityIdentifier`を指定していることに注目して下さい。

```swift:EarlGreySwiftSampleAppTests.swift(EarlGreySwiftSampleAppTestsターゲット)
import XCTest
import EarlGrey
@testable import EarlGreySwiftSampleApp

class EarlGreySwiftSampleAppTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
    }
    
    override func tearDown() {
        super.tearDown()
    }
        
    func testConfirmAge() {
        
        var age = "foo"
        // Tests for strings
        EarlGrey.select(elementWithMatcher: grey_accessibilityID("ageTextField"))
            .perform(GREYActions.action(forTypeText: age))
        EarlGrey.select(elementWithMatcher: grey_accessibilityID("confirmAgeButton"))
            .perform(grey_tap())
        EarlGrey.select(elementWithMatcher: grey_text("Error"))
            .assert(with: grey_sufficientlyVisible())
        EarlGrey.select(elementWithMatcher: grey_text("OK"))
            .perform(grey_tap())
        
        age = "-123"
        // Tests for integers less than 0
        EarlGrey.select(elementWithMatcher: grey_accessibilityID("ageTextField"))
            .perform(grey_clearText())
            .perform(GREYActions.action(forTypeText: age))
        EarlGrey.select(elementWithMatcher: grey_accessibilityID("confirmAgeButton"))
            .perform(grey_tap())
        EarlGrey.select(elementWithMatcher: grey_text("Error"))
            .assert(with: grey_sufficientlyVisible())
        EarlGrey.select(elementWithMatcher: grey_text("OK"))
            .perform(grey_tap())
        
        age = "20"
        // Test for integers greater than or equal to 0
        EarlGrey.select(elementWithMatcher: grey_accessibilityID("ageTextField"))
            .perform(grey_clearText())
            .perform(GREYActions.action(forTypeText: age))
        EarlGrey.select(elementWithMatcher: grey_accessibilityID("confirmAgeButton"))
            .perform(grey_tap())
        EarlGrey.select(elementWithMatcher: grey_text("Confirmed"))
            .assert(with: grey_sufficientlyVisible())
        EarlGrey.select(elementWithMatcher: grey_text("OK"))
            .perform(grey_tap())
    }
    	
}
```

# 自動UIテストの実行
通常のユニットテストと同様に`Cmd + U`により、テストを実行します。上記コードの通り、入力フィールドに対して、文字列、負の整数、正の整数を順番にテストしてUI自動テストが実行される様子を確認できると思います。

# 最後に
いかがでしたでしょうか？もしかしたら、テストするためにそれなりに事前準備必要じゃねーか？と思っている方もいらっしゃるかもしれません！？...が、以前は様々なXcode設定を手動を行う必要がありました。大きな進歩です。また、そんなことはどういでもいいくらい、十分な価値があるフレームワークだと私は思っています。気になる点があれば積極的にぜひコントリビュートしていきましょう😉
なお、もちろん、EarlGreyだけで全てのUIテストがカバーできるわけではありません。要所要所で必要に応じて他のテストツールを組み合わせて効率よく素敵なアプリ開発を進めていきたいですね🎵

[^1]: 正確にはEarlGrey 1.4.0からSwift 3には対応していましたが、当時は別途Objective-Cブリッジングヘッダを追加する必要があったり、Swift 3っぽくない表記(Swift 2っぽい表記をひきずったまま)でした。