[Swift] MockとStubだけじゃない！テストダブルの分類

# テストダブルとは

ユニットテストで、テスト対象が依存しているオブジェクトを置き換える代用品のことです。
テストダブルは以下の5つに分類されます。

- **Dummy Object**
- **Test Stub**
- **Test Spy**
- **Mock Object**
- **Fake Object**

それぞれの違いをSwiftで簡単な例を示しながら説明します。

例では `Repository` に依存する `Subject` の `method()` のテストを考えます。

```swift:テスト対象
struct Item {
    let value: Int
}

protocol Repository {
    func save(_ item: Item)
    func get() -> Item?
}

struct Subject {
    private let repository: Repository
    init(repository: Repository) {
        self.repository = repository
    }

    // テスト対象のメソッド
    func method() { ... }
}
```

# Dummy Object

**テストに影響を与えないテストダブル**です。

テストの成功/失敗に干渉しない依存である場合には、Dummyを渡します。

<details><summary>Swiftでの例</summary>

```swift:テスト対象のメソッド
    func method(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
```

```swift:テストコード
import XCTest

struct DummyRepository: Repository {
    func save(_ item: Item) {}
    func get() -> Item? { return nil }
}

final class UnitTests: XCTestCase {
    func test() {
        let subject = Subject(repository: DummyRepository())
        XCTAssertEqual(2, subject.method(1, 1))
    }
}
```
</details>

# Test Stub

間接入力の値をセットして、**決められた値を返すテストダブル**です。

間接入力とは、テストコードからテストダブルを通してテスト対象へ伝える入力のことです。

<details><summary>Swiftでの例</summary>

```swift:テスト対象のメソッド
    func method() -> Int {
        let item = repository.get()
        return item?.value ?? 0
    }
```

```swift:テストコード
import XCTest

final class StubRepository: Repository {
    var 間接入力値: Item?
    
    func save(_ item: Item) {}
    func get() -> Item? {
        return 間接入力値
    }
}

final class UnitTests: XCTestCase {
    func test() {
        let repo = StubRepository()
        let subject = Subject(repository: repo)
        repo.間接入力値 = Item(value: 10)
        XCTAssertEqual(10, subject.method())
    }
}
```
</details>

# Test Spy

**間接出力の値を保持**して、テストコードから参照します。

間接出力とは、テスト対象からテストダブルを通してテストコードへ伝わる出力のことです。
送られてきた機密情報を敵国(テストコード)に流すスパイのイメージです。

Test Spy は Test Stub の機能を含みます。(間接入力をしてもよい)

<details><summary>Swiftでの例</summary>

```swift:テスト対象のメソッド
    func method(value: Int) {
        let item = Item(value: value)
        repository.save(item)
    }
```

```swift:テストコード
import XCTest

final class StubRepository: Repository {
    var 間接入力値: Item?
    var 間接出力値: Item?
    
    func save(_ item: Item) {
        間接出力値 = item
    }
    
    func get() -> Item? {
        return 間接入力値
    }
}

final class UnitTests: XCTestCase {
    func test() {
        let repo = StubRepository()
        let subject = Subject(repository: repo)
        subject.method(value: 10)
        XCTAssertEqual(10, repo.間接出力値?.value)
    }
}
```
</details>

# Mock Object

**間接出力の値をMock Object内で検証**します。

Mock Object は Test Stub の機能を含みます。(間接入力をしてもよい)

<details><summary>Swiftでの例</summary>

```swift:テスト対象のメソッド
    func method(value: Int) {
        let item = Item(value: value)
        repository.save(item)
    }
```

```swift:テストコード
import XCTest

final class MockRepository: Repository {
    var 間接入力値: Item?
    var 期待する値: Int!
    private(set) var テスト成功 = false
    
    func save(_ item: Item) {
        XCTAssertEqual(期待する値, item.value)
        テスト成功 = true
    }
    
    func get() -> Item? {
        return 間接入力値
    }
}

final class UnitTests: XCTestCase {
    func test() {
        let repo = MockRepository()
        let subject = Subject(repository: repo)
        repo.期待する値 = 10
        subject.method(value: 10)
        XCTAssertTrue(repo.テスト成功)
    }
}
```

</details>

# Fake Object

**本物と同じように動作**します。インメモリのリポジトリはこれにあたります。

<details><summary>Swiftでの例</summary>

```swift:テスト対象のメソッド
    func method(value: Int) {
        let item = Item(value: value)
        repository.save(item)
    }
```

```swift:テストコード
import XCTest

final class FakeRepository: Repository {
    private var item: Item?
    
    func save(_ item: Item) {
        self.item = item
    }
    
    func get() -> Item? {
        return item
    }
}

final class UnitTests: XCTestCase {
    func test() {
        let repo = FakeRepository()
        let subject = Subject(repository: repo)
        subject.method(value: 10)
        XCTAssertEqual(10, repo.get()?.value)
    }
}
```
</details>

# まとめ

Test Double の分類をまとめるとこんな感じです。
↓
本物と同じように動く [yes]→ **Fake Object**
↓[no]
テストに影響を及ぼさない [yes]→ **Dummy Object**
↓[no]
間接出力の値を内部で検証 [yes]→ **Mock Object**
↓[no]
間接出力の値を保持 [yes]→ **Test Spy**
↓[no]
間接入力ができる **Test Stub**

違いを意識して呼び分けるようにしましょう！

# 参考

https://ja.m.wikipedia.org/wiki/テストダブル

https://goyoki.hatenablog.com/entry/20120301/1330608789