やさしいSwift単体テスト~テスト可能なクラス設計・後編~

# 概要
- 前編: テスト対象と、テストが書きづらいコードはなぜ書きづらいのかを説明します。
    - [やさしいSwift単体テスト~テスト可能なクラス設計・前編~](http://qiita.com/yokoya-s/items/b00012c8b1a84238becf)

- 後編(この記事): テストが書きづらいコードを書きやすいコードへ変更する方法、実際のテストコードを説明します。

# テストが書けない/書きづらいコードの原因と回避策

前編のまとめから話を続けます。

## クラス外の値を内部で利用している場合

- テストを行うための値を準備できない
    - クラス外の値 = どんなものが返ってくるか分からない = テストできない

    → **テスト時のみ、返ってくる値を固定値にする(= 返ってくる値が明確)** ことで回避します。

<p></p>

- テスト失敗時に、原因がクラス内/外どちらなのか明確にできない
    - クラス外の値 = どんな実装になっているかわからない = テスト失敗時に原因が明確にできない

    → **クラス外とのやりとりはProtocolを通すことで、実際の実装との関係を切り離す** ことで回避します。

## 大域変数(UserDefaultsなど)を利用している場合

- 与えた変更を消し忘れると、本体実行時や他のテストに影響する

    → **大域変数と直接やりとりを行わないようにし、大域変数への変更は最小限にとどめる** ことで回避します。

　　
以降では、上記の要望を満たすための方法を具体例とともに説明します。　
[暗黙的入力のテスト](#暗黙的入力のテスト) と、 [暗黙的出力のテスト](#暗黙的出力のテスト) と２つに分かれますが、行う作業はほぼ同じです。


# 暗黙的入力のテスト

- 暗黙的**入力** = クラス外から値を取得している = テストが書けない/書きづらい

例えば、以下の本体コードがあるとします。

```swift:
class ImplicitInput {
    private let data: Data

    init() {
        // 1 ~ 10 までのランダムな数字を使って Data クラスを生成する
        let random = Int(arc4random_uniform(10) + 1)
        self.data = Data(value: random)
    }

    func reduce () -> Int {
        // クラス外から値を取得している
        return self.data.double() - 1
    }



    class Data {

        let value: Int

        init(value: Int) {
            self.value = value
        }

        func double() -> Int {
            return value * 2
        }
    }
}

```

`ImplicitInput#reduce()` の部分で、暗黙的入力を行なっています。

現状では「`Data#double()` = 4 の時、 `ImplicitInput#reduce()` = 3 となる」といったテストは書けません。
しかし、 `Data#double()` の値を指定できるようになれば、テストを書けるようになります。

=> **スタブ** というオブジェクトを利用する手法を紹介します。

## スタブとは

![単体テストのハジメver2.019.jpeg](https://qiita-image-store.s3.amazonaws.com/0/97259/90da2d8d-e313-72bc-0384-b7463e51d86d.jpeg)

- スタブ = 事前に設定した振る舞いをする **偽物のオブジェクト**。特に、取得用のオブジェクトとして利用する。

## スタブを利用すると何が良いのか

スタブを利用することで、 冒頭で示した 3つの回避策

- テスト時のみ、返ってくる値を固定値にする(= 返ってくる値が明確)
- クラス外とのやりとりはProtocolを通すことで、実際の実装との関係を切り離す
- 大域変数と直接やりとりを行わないようにし、大域変数への変更は最小限にとどめる

を全て満たすことができます。

本番時には 「本物の振る舞い」をするオブジェクトを利用し、
テスト時には「偽物の振る舞い」をするオブジェクト(スタブ)に差し替える。
そうすることで、本番の動きは変えずにテストを書けるようになります。

## スタブを利用するために本体コードを変更する

スタブへ差し替え可能にするためには準備が必要です。

- 必要な作業
    1. 制御したい振る舞いをProtocol定義する
    1. 暗黙的入力を行なっている箇所でProtocolを利用する
    1. 本番用にProtocolに準拠したクラスを定義する

### 1. 制御したい振る舞いをProtocol定義する

「クラス外から取得した値」に相当するProtocolを定義します。
例題の場合は、`Data#double()` です。

```swift:

// 取得用のプロトコル
protocol ReadableRepositoryContract {

    // Data#double() に相当する
    func read() -> Int

}
```

### 2. 暗黙的入力を行なっている箇所でProtocolを利用する

次に、定義したProtocolを本体コードで利用します。

変更後のコード:

```swift:

class ImplicitInput {

    // - 変更1
    private let repository: ReadableRepositoryContract

    // - 変更2
    init (readVia repository: ReadableRepositoryContract) {
        self.repository = repository
    }

    func reduce() -> Int {

        // - 変更3
        return self.repository.read() - 1
    }
}
```

- 変更1: 取得用の `ReadableRepositoryContractプロトコル` を プロパティで持つ
- 変更2: プロパティの実態は 初期化時に `init` の引数としてもらう
- 変更3: `ImplicitInput#reduce()` 内では、`ReadableRepositoryContractプロトコル` を利用する

上記変更のおかげで、テスト時に `ReadableRepositoryContractプロトコル` に準拠した偽物のオブジェクト(スタブ)を利用できるようになります。

### 3. 本番用にProtocolに準拠したクラスを定義する

本番時に利用するオブジェクトは別途必要になるので、元となるクラスを定義します。

```swift:

// 本番用の ReadableRepositoryContractプロトコル に準拠したクラス
class ReadableRepository: ReadableRepositoryContract {

    private let data: ImplicitInput.Data

    init(data: ImplicitInput.Data) {
        self.data = data
    }

    // プロトコル準拠部分
    // Data#double() に相当する
    func read() -> Int {
        return self.data.double()
    }

}
```

今回の例題では `Dataクラス` から値を取得していますが、 `UserDefaults` から取得する場合は以下のようになります。

```swift:
class ReadableRepository: ReadableRepositoryContract {

    func read() -> Int {
        return UserDefaults.standard.integer(forKey: "read")
    }

}
```


## 本体コード変更後

以上でスタブを利用する準備は完了です。

ちなみに、 本番で`ImplicitInputクラス` を使う時は以下の書き方になります。

```swift

// 本番の取得用オブジェクトを生成
let repository = ReadableRepository(
    data: ImplicitInput.Data(value: 5)
)

let implicitInput = ImplicitInput(readVia: repository)

// ReadableRepositoryContract を経由して、 Dataクラス から値を取得する
let result = implicitInput.reduce()

```

## テストを書く

スタブを使う準備が整ったので、実際に `ImplicitInputクラス` のテストコードを書いていきます。

### 1. スタブを定義する

スタブ = 取得用の偽物のオブジェクト = 取得用のプロトコル(`ReadableRepositoryContract`) に準拠しています。

```swift:

// スタブを定義する
class ReadableRepositoryStub: ReadableRepositoryContract {

    private let base: Int

    init(base: Int) {
        self.base = base
    }

    // 偽物の振る舞いを行なっている箇所
    func read() -> Int {
        // init時に渡された値をそのまま返す
        return self.base
    }

}
```

### 2. スタブを利用してテストを書く

本体コードを変更する前は、「`Data#double()` = 4 の時、 `ImplicitInput#reduce()` = 3 となる」というテストを書こうとしても書けませんでした。
変更後のコードで、「`ReadableRepositoryContract#read()` = 4 の時、`ImplicitInput#reduce()` = 3 となる」というテストを書いてみます。

```swift:

class ImplicitInputTests: XCTestCase {

    func testMultiplication() {
        // ReadableRepositoryContract#read() = 4 の時、
        let int = 4

        // ImplicitInput#reduce() = 3 となる
        let expected = 3

        // - 1: スタブを作成
        let repositoryStub = ReadableRepositoryStub(readValue: int)

        // - 2: スタブを差し込む
        let input = ImplicitInput(readVia: repositoryStub)

        // - 3: 内部でスタブが利用される
        //      これにより、「ReadableRepositoryContract#read() = 4 の時」を再現できる
        let actual = input.reduce()

        XCTAssertEqual(actual, expected)

    }

}
```

無事、テストが書けるようになりました。

## スタブ説明のまとめ

[スタブを利用すると何が良いのか](#スタブを利用すると何が良いのか)

- テスト時のみ、返ってくる値を固定値にする(= 返ってくる値が明確) → [該当](#2-スタブを利用してテストを書く)
    - テストを行う前の準備が用意になり、テストが書きやすくなった
- クラス外とのやりとりはProtocolを通すことで、実際の実装との関係を切り離す → [該当](#2-暗黙的入力を行なっている箇所でProtocolを利用する)
    - `ImplicitInputクラス` 内では `ReadableRepositoryContractプロトコル` を利用することにより、他のクラスの実装から切り離すことができた  
      なので、`ImplicitInputクラス` のテストが失敗した場合は、`ImplicitInputクラス` の実装が間違っているということが明確になった
- 大域変数と直接やりとりを行わないようにし、大域変数への変更は最小限にとどめる → [該当](#3-本番用にProtocolに準拠したクラスを定義する)
    - `ReadableRepositoryContractプロトコル` を通すことで、大域変数と直接やりとりを行わなくなった  
      本番時に大域変数を利用する箇所は、テスト時に偽のオブジェクトへ差し替えることで、大域変数への変更を行わなくて済むようになった


# 暗黙的出力のテスト

- 暗黙的**出力** = 処理の結果または過程で、クラス外へ変更を行っている = テストが書けない/書きづらい

例として、以下の本体コードがあるとします。

```swift:

class ImplicitOutput {

    private let data = Data(value: nil)

    func write(int: Int) {

        // クラス外へ変更を行っている
        self.data.value = int
    }

    class Data {

        var value: Int?

        init(value: Int?) {
            self.value = value
        }
    }

}

```

`ImplicitOutput#write(:Int)` の部分で、暗黙的出力を行なっています。
現状では、「`ImplicitOutput#write(:Int)` に 2 を渡した時、`Data.value` = 2 となる」 といったテストは書けません。

=> **スパイ** というオブジェクトを利用する手法を紹介します。

## スパイとは

![単体テストのハジメver3.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/97259/2666681c-b808-c07c-a66e-ac3cf0f61d66.jpeg)

- スパイ = 事前に設定した振る舞いをする**偽物の関数やオブジェクト**。関数の呼び出し回数や、自身への変更を記録するオブジェクトとして利用する。

## スパイを利用すると何が良いのか

スパイを利用することで、 冒頭で示したウチの 2つの回避策

- クラス外とのやりとりはProtocolを通すことで、実際の実装との関係を切り離す
- 大域変数と直接やりとりを行わないようにし、大域変数への変更は最小限にとどめる

を満たすことができます。

## スパイを利用するために本体コードを変更する

スパイの利用には準備が必要です。

- 必要な作業
    1. 制御したい振る舞いをProtocolにする
    1. 暗黙的出力を行なっている箇所ではProtocolを利用する
    1. 本番用にProtocolに準拠したオブジェクトを作成する


### 1. 制御したい振る舞いをProtocolにする

「記録したいクラス外の値」に相当するProtocolを定義します。
例題の場合は、`Data#value` です。

```swift:

// 記録用のプロトコル
protocol WritableRepositoryContract {

    // Data#value に相当する
    func write(int: Int)

}
```

### 2. 暗黙的出力を行なっている箇所ではProtocolを利用する

次に、定義したProtocolを本体コードで利用します。

変更後のコード:

```swift:

class ImplicitOutput {

    // - 変更1
    private let repository: WritableRepositoryContract

    // - 変更2
    init(writeVia repository: WritableRepositoryContract) {
        self.repository = repository
    }

    func write(int: Int) {

        // - 変更3
        self.repository.write(int: int)

    }

}
```

- 変更1: 記録用の `WritableRepositoryContractプロトコル` を プロパティで持つ
- 変更2: プロパティの実態は 初期化時に `init` の引数としてもらう
- 変更3: `ImplicitOutput#write(:Int)` 内では、`WritableRepositoryContractプロトコル` を利用する

上記変更のおかげで、テスト時に `WritableRepositoryContractプロトコル` に準拠した偽物のオブジェクト(スパイ)を利用できるようになります。

### 3. 本番用にProtocolに準拠したクラスを定義する

本番時に利用するオブジェクトは別途必要になるので、元となるクラスを定義します。

```swift:

// 本番の WritableRepositoryContractプロトコル に準拠したクラス
class WritableRepository: WritableRepositoryContract {

    private let data: ImplicitOutput.Data

    init(data: ImplicitOutput.Data) {
        self.data = data
    }

    // プロトコル準拠部分
    // Data#value に相当する
    func write(int: Int) {
        self.data.value = int
    }

}
```

今回の例題では Dataクラス から値を取得していますが、 UserDefaults を変更場合は以下のようになります。

```swift:

class WritableRepository: WritableRepositoryContract {

    func write(int: Int) {
        UserDefaults.standard.set(int, forKey: "write")
    }

}
```

## 変更後の本体コード

以上でスパイを利用する準備は完了です。

ちなみに、 本番で `ImplicitOutputクラス` を使う時は以下の書き方になります。

```swift:

// 本番の記録用オブジェクトを生成
// ただし、本番では 記録 の必要はないので 変更 のために使用している
let repository = WritableRepository(
    data: ImplicitOutput.Data(value: nil)
)

let implicitOutput = ImplicitOutput(writeVia: repository)

// WritableRepositoryContractを経由して、Dataクラス の値を変更する
implicitOutput.write(int: 2)

```

## テストを書く

スパイを使う準備が整ったので、スパイを使って実際にテストコードを書いていきます。

### 1. スパイオブジェクトを書く

スパイ = 記録用の偽物のオブジェクト = 記録用のプロトコル(`WritableRepositoryContract`) に準拠している

```swift:

// スパイを定義する
class WritableRepositorySpy: WritableRepositoryContract {

    // メソッドが呼び出された際の引数列。
    private(set) var callArguments: [Int] = []

    // 偽物の振る舞いを行なっている箇所
    func write(int: Int) {
        // 呼び出しを記録してい
        self.record(int)
    }

    private func record(_ args: Int) {
        self.callArguments += [args]
    }

}
```

### 2. スパイを使ってテストを書く

本体コードを変更する前は、「`ImplicitOutput#write(:Int)` へ 2 を渡した時、`Data.value` = 2 となる」というテストを書こうとしても書けませんでした。
変更後のコードで、「`ImplicitOutput#write(:Int)` へ 2 を渡した時、 `WritableRepositoryContract#write(:Int)` へ 同じ値 を渡している」というテストを書いてみます。

```swift:

class ImplicitOutputTests: XCTestCase {

    func testWrite() {
        // ImplicitOutput#write(:Int) へ 2 を渡した時、
        let int = 2

        // WritableRepositoryContract#write(:Int) へ 同じ値 を渡している
        let expected = int

        // - 1: スパイを作成する
        let spy = WritableRepositorySpy()

        // - 2: スパイを差し込む
        let output = ImplicitOutput(writeVia: spy)

        // - 3: 内部でスパイに記録される
        output.write(int: int)

        // - 4: スパイに記録された値を確認する
        XCTAssertEqual(expected, spy.callArguments.first!)
    }

}
```
無事、テストが書けるようになりました。

## スパイ説明のまとめ

[スパイを利用すると何が良いのか](#スパイを利用すると何が良いのか)

- クラス外とのやりとりはProtocolを通すことで、実際の実装との関係を切り離す
    - `ImplicitOutputクラス` 内では `WritableRepositoryContractプロトコル` を利用することにより、`Dataクラス` の実装から切り離すことができた  
      なので、`ImplicitOutputクラス` のテストが失敗した場合は、`ImplicitOutputクラス` の実装が間違っているということが明確になった
- 大域変数と直接やりとりを行わないようにし、大域変数への変更は最小限にとどめる
    - `WritableRepositoryContractプロトコル` を通すことで、大域変数と直接やりとりを行わなくなった  
    本番時に大域変数を利用する箇所は、テスト時に偽のオブジェクトへ差し替えることで、大域変数への変更を行わなくて済むようになった

# おまけ: Repositoryのテスト

`ReadableRepository` と `WritableRepository` ですが、もちろんこれらもテストする必要があります。

`Repository` は `ImplicitXxxxクラス` をテストするために必要になったクラスですが、それ以外にも利点があります。
クラス外からの取得/クラス外への変更 のテストを `ImplicitXxxxクラス` から **分離** できた、という利点です。
テストを分けられるということは、1つのテストで確認する事柄が減るということであり、**テストの書きやすさにつながります。**

以降には、 `Repository` 経由で UserDefaults の値を取得/変更 している想定でのテストを載せています。
~~この階層まできたら、UserDefaults への変更はもう致し方なしと思っています。。。~~
[追記: UserDefaults への直接操作を回避する](#追記-userdefaults-への直接操作を回避する)

## ReadableRepositoryのテスト

```swift:

// 本体コード
class ReadableRepositoryForUserDefaults: ReadableRepositoryContract {

    func read() -> Int {
        return UserDefaults.standard.integer(forKey: "read")
    }

}

// テストコード
class ReadableRepositoryTests: XCTestCase {
    func testRead() {
        let testValue = 2
        let expected = testValue

        // 本体コード、もしくは他のテストから変更されている可能性があるので、UserDefaultsを初期状態にしてからスタート
        UserDefaults.standard.removeObject(forKey: "read")

        // テストを行うために 「事前に入っていて欲しい値」をUserDefaultsに直接書き込む
        UserDefaults.standard.set(testValue, forKey: "read")

        /* Repositoryをテスト */
        let repository = ReadableRepositoryForUserDefaults()
        let actuals = repository.read()
        XCTAssertEqual(actuals, expected)
        /* Repositoryをテスト */

        // 本体コード、もしくは他のテストへ 影響を与えないために、UserDefaultsに加えた変更を取り消す
        UserDefaults.standard.removeObject(forKey: "read")
    }

}
```

## WritableRepositoryのテスト

```swift:

// 本体コード
class WritableRepositoryForUserDefaults: WritableRepositoryContract {

    func write(int: Int) {
        UserDefaults.standard.set(int, forKey: "write")
    }

}

// テストコード
class WritableRepositoryTests: XCTestCase {
    func testWrite() {
        let testValue = 2
        let expected = testValue

        // 本体コード、もしくは他のテストから変更されている可能性があるので、UserDefaultsを初期状態にしてからスタート
        UserDefaults.standard.removeObject(forKey: "write")

        /* Repositoryをテスト */
        let repository = WritableRepositoryForUserDefaults()
        repository.write(int: testValue)
        let actuals = UserDefaults.standard.integer(forKey: "write")
        XCTAssertEqual(actuals, expected)
        /* Repositoryをテスト */

        // 本体コード、もしくは他のテストへ 影響を与えないために、UserDefaultsに加えた変更を取り消す
        UserDefaults.standard.removeObject(forKey: "write")
    }
}
```


# 後半まとめ

- 暗黙的入力のテスト: Protocolを使ってスタブ(取得用の偽物のオブジェクト)を使えるクラス設計にしよう
- 暗黙的出力のテスト: Protocolを使ってスパイ(記録用の偽物のオブジェクト)を使えるクラス設計にしよう

- メリット:
    - 偽物に差し替えられるとテストが書きやすい🎉
    - 結果的に疎結合になる(`UserDefaults`にべったり依存😱などがなくなる)


# おわりに

スタブ・スパイ に初対面だったという方、いかがだったでしょうか。めんどくさかったですか？
私はとてもめんどくさいと思いました(テストを書くようになった当初の話です)。
あと、テストを書くのも本体コードを書くのもめちゃくちゃ時間がかかりました。

「テスト可能な本体コードの書き方」がぜんっぜん身につかなくて投げ出したかったのですが、
[車窓からの TDD](http://objectclub.jp/technicaldoc/testing/stack_tdd.pdf)を読んで（実践して）からだいぶ改善されました。
「テストコード書くようになったけど、時間かかってつらい」 という方はそちらの記事も参考にして見てください。


## 追記: UserDefaults への直接操作を回避する
回避策を[コメント](https://qiita.com/yokoyas000/items/327fb2b481738fac4c5a#comment-dc8fc8d0d5abbf68b32a)でいただけました。ありがとうございます！

下記手順でUserDefaults への直接操作を回避します。

- UserDefaults の必要な部分だけのプロトコルを定義
- 各Repositoryではプロトコル経由でUserDefaultsと接続する


まずは、UserDefaultsのプロトコル定義部分です。

```swift:
// 取得用のプロトコル
protocol UserDefaultsReadableContract {
    func integer(forKey defaultName: String) -> Int
    func double(forKey defaultName: String) -> Double
}

// 記録用のプロトコル
protocol UserDefaultsWritableContract {
    func set(_ value: Int, forKey defaultName: String)
    func set(_ value: Double, forKey defaultName: String)
}
```

次に、本物のUserDefaultsに先ほど定義したのプロトコルを extension します。
これにより、`UserDefaultsReadableContract`、 `UserDefaultsWritableContract` を利用している箇所で
UserDefaults のインスタンスを渡すことが可能になります。
また、今後 Swift バージョンアップなどで UserDefaults のインターフェースが変わった場合、
この部分でコンパイルエラーとなるのでプロトコル定義の間違いに気づけます。

```swift:
extension UserDefaults: UserDefaultsReadableContract, UserDefaultsWritableContract {}
```

各Repositoryがプロトコル経由でUserDefaultsと接続するように変更します。

```swift:ReadableRepositoryForUserDefaults
class ReadableRepositoryForUserDefaults: ReadableRepositoryContract {

    private let userDefaults: UserDefaultsReadableContract

    // 本体コード側では
    // ReadableRepositoryForUserDefaults(via: UserDefaults.standard) と
    // 宣言することで UserDefaults を利用できる
    init(via userDefaults: UserDefaultsReadableContract) {
        self.userDefaults = userDefaults
    }

    func read() -> Int {
        return self.userDefaults.integer(forKey: "read")
    }
}
```

```swift:WritableRepositoryForUserDefaults
class WritableRepositoryForUserDefaults: WritableRepositoryContract {

    private let userDefaults: UserDefaultsWritableContract

    // 本体コード側では
    // WritableRepositoryForUserDefaults(via: UserDefaults.standard) と
    // 宣言することで UserDefaults を利用できる
    init(via userDefaults: UserDefaultsWritableContract) {
        self.userDefaults = userDefaults
    }

    func write(int: Int) {
        self.userDefaults.set(int, forKey: "write")
    }

}
```

最後に、テストの部分です。

```swift:ReadableRepositoryのテスト
// 新しく定義した UserDefaultsReadableContract のスタブ(取得用の偽物)を作成する
struct UserDefaultsStub: UserDefaultsReadableContract {

    private let keyAndValues: [String: Any]

    init(keyAndValues:  [String: Any] = [:]) {
        self.keyAndValues = keyAndValues
    }

    func integer(forKey defaultName: String) -> Int {
        // テスト時に値を設定し忘れて nil だった場合、実行時エラーで気づける
        return self.keyAndValues[defaultName] as! Int
    }

    func double(forKey defaultName: String) -> Double {
        return self.keyAndValues[defaultName] as! Double
    }

}

// テストコード
class ReadableRepositoryTests: XCTestCase {
    func testRead() {
        let testValue = 2
        let expected = testValue

        // - 1: スタブを作成
        let userDefaultsStub = UserDefaultsStub(
            keyAndValues: ["read": testValue]
        )

        // - 2: スタブを差し込む
        let repository = ReadableRepositoryForUserDefaults(via: userDefaultsStub)

        // - 3: 内部でスタブが利用される
        let actuals = repository.read()
        XCTAssertEqual(actuals, expected)
    }
}
```

```swift:WritableRepositoryのテスト
// 新しく定義した UserDefaultsWritableContract のスパイ(記録用の偽物)を作成する
class UserDefaultsSpy: UserDefaultsWritableContract {

    private(set) var callSetIntArguments: [String: Int] = [:]
    private(set) var callSetDoubleArguments: [String: Double] = [:]

    func set(_ value: Int, forKey defaultName: String) {
        self.callSetIntArguments[defaultName] = value
    }

    func set(_ value: Double, forKey defaultName: String) {
        self.callSetDoubleArguments[defaultName] = value
    }
}

class WritableRepositoryTests: XCTestCase {
    func testWrite() {
        let testValue = 2
        let expected = testValue

        // - 1: スパイを作成する
        let userDefaultsSpy = UserDefaultsSpy()

        // - 2: スパイを差し込む
        let repository = WritableRepositoryForUserDefaults(via: userDefaultsSpy)

        // - 3: 内部でスパイに記録される
        repository.write(int: testValue)

        // - 4: スパイに記録された値を確認する
        let actual = userDefaultsSpy.callSetIntArguments["write"]!
        XCTAssertEqual(actual, expected)
    }
}
```