【まとめ】Engineering for Testability (後半)【WWDC 2017】

# はじめに

この記事では、WWDC 2017でのセッション [Engineering for Testability](https://developer.apple.com/videos/play/wwdc2017/414/)の後半の内容をまとめます。
[前半](http://qiita.com/MasayaHayashi724/items/20bc21e194141240f80d)を先に読んでいただけると良いと思いますが、一応前半を読まなくても後半を理解できる内容になっていると思います。
このセッションでは、テストがしやすいコードの書き方や、保守・スケールしやすいテストコードの書き方などが紹介されました。
また、テストコードの大切さについても話されています。
図は[Appleが公開しているスライド](https://devstreaming-cdn.apple.com/videos/wwdc/2017/414qr3121b1oieq/414/414_engineering_for_testability.pdf)から引用しました。

# 読むのがめんどくさい方へ

結構長いので、すべて読むのがめんどくさい方はこれだけでも覚えていってください。

> Treat your test code with the same amount of care as your app code
> アプリのコードと同じくらいテストコードに気を配ろう

> Code reviews for test code, not code reviews with test code
> テストコードと一緒にレビューするのではなく、テストコード自体をレビューしよう

つまり、

<img width="484" alt="スクリーンショット 2017-06-20 19.48.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/149003/ac971885-699a-3f13-f079-da68f2420b6b.png">

ではなく、

<img width="373" alt="スクリーンショット 2017-06-20 19.49.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/149003/ff0d7d62-f8db-4d8d-9614-f06abf26cc2c.png">

である。

# まとめ

## Scalable Test Code 〜スケールしやすいテストを書く〜

### テストのピラミッド

UI TestとUnit Testの割合は次のピラミッドのようになるようにバランスを保つと良い。

<img width="659" alt="スクリーンショット 2017-06-20 20.20.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/149003/c962cc57-ce3d-837e-73c2-f048472bb221.png">

### 保守・スケールしやすいテストを書く方法

- クエリを抽象化する
- 一連の処理を行うオブジェクトやユーティリティ関数を利用する
- キーボードショートカットを簡単に使用する

### クエリの抽象化

次のような一連のUI Testは、

```swift
app.buttons["blue"].tap()
app.buttons["red"].tap()
app.buttons["yellow"].tap()
app.buttons["green"].tap()
app.buttons["purple"].tap()
app.buttons["orange"].tap()
app.buttons["pink"].tap()
```

次のようにクエリの一部を変数で保持し、複雑なクエリはラップします。

```swift
func tapButton(_ color: String) {
    app.buttons[color].tap()
}

let colors = ["blue", "red", "yellow", "green", "purple", "orange", "pink"]
for color in colors {
    tapButton(color)
}
```

### オブジェクトやユーティリティ関数の定義

次のような一連のUI Testは、ゲームの設定画面を開き、難易度を初心者に設定し、サウンドをOFFにして最初の画面に戻る動作をテストしています。
しかし、このテストコードに馴染みのない人から見ると非常に読みづらいテストコードとなってしまっています。

```swift
func testGameWithDifficultyBeginnerAndSoundOff() {
    app.navigationBars["Game.GameView"].buttons["Settings"].tap()
    app.buttons["Difficulty"].tap()
    app.buttons["beginner"].tap()
    app.navigationBars.buttons["Back"].tap()
    app.buttons["Sound"].tap()
    app.buttons["off"].tap()
    app.navigationBars.buttons["Back"].tap()
    app.navigationBars.buttons["Back"].tap()

    // test code
}
```

そこで、次のように難易度設定とサウンド設定を行うテストをラップします。
`enum`で管理するようにしておくと良いでしょう。

```swift
enum Difficulty {
    case beginner
    case intermediate
    case veteran
}
enum Sound {
    case on
    case off
}
func setDifficulty(_ difficulty: Difficulty) {
    app.buttons["Difficulty"].tap()
    app.buttons[difficulty.rawValue].tap()
    app.navigationBars.buttons["Back"].tap()
}
func setSound(_ sound: Sound) {
    app.buttons["Sound"].tap()
    app.buttons[sound.rawValue].tap()
    app.navigationBars.buttons["Back"].tap()
}
```

これで次のようにテストを書き直すことができます。
しかし、まだ分かりにくい部分があります。

```swift
func testGameWithDifficultyBeginnerAndSoundOff() {
    app.navigationBars["Game.GameView"].buttons["Settings"].tap()
    setDifficulty(.beginner)
    setSound(.off)
    app.navigationBars.buttons["Back"].tap()

    // test code
}
```

そこで、先ほどの`enum`などを持った次のようなクラスを定義します。

```swift
class GameApp: XCUIApplication {
    enum Difficulty { /* cases */ }
    enum Sound { /* cases */ }
    func setDifficulty(_ difficulty: Difficulty) { /* code */ }
    func setSound(_ sound: Sound) { /* code */ }

    func configureSettings(difficulty: Difficulty, sound: Sound) {
        app.navigationBars["Game.GameView"].buttons["Settings"].tap()
        setDifficulty(difficulty)
        setSound(sound)
        app.navigationBars.buttons["Back"].tap()
    }
}
```

上記のクラスに持たせた`configureSetting(difficulty:sound:)`メソッドを利用すると、次のような非常に分かりやすいテストコードを書くことができます。
必要であれば、Xcode9から利用できる`XCTContext.runActivity(named name: String, block: (XCTActivity))`でテストレポートを見やすくするのも良いでしょう([参考](http://qiita.com/MasayaHayashi724/items/72d08848e404683277f6))。

```swift
func testGameWithDifficultyBeginnerAndSoundOff() {

    GameApp().configureSettings(difficulty: .beginner, sound: .off)

    // test code

}
```

### キーボードショートカット

これはmacOSアプリでの話になりますが、メニューバーにある機能を使用するとき、以前までは次のようにUI Testを書いていました。

```swift
let menuBarsQuery = app.menuBars
menuBarsQuery.menuBarItems["Format"].click()
menuBarsQuery.menuItems["Font"].click()
menuBarsQuery.menuItems["Show Colors"].click()
```

ですが、もしその機能にショートカットキーが割り当てられているなら、次のように簡単にその機能を呼び出すテストを書くことができます。

```swift
app.typeKey("c", modifierFlags:[.command, .shift]) 
```

# さいごに

テストの書き方と大切さが分かるセッションだったと思います。
iOSアプリにテストを導入するきっかけになれば嬉しいです。
最後に、記事の内容に誤りなどがございましたら、気軽に、優しく、コメントしていただけると嬉しいです。
それでは、最後までお読みいただき、ありがとうございました！