LoginSignup
1
2

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

Posted at

テストダブルとは

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

  • Dummy Object
  • Test Stub
  • Test Spy
  • Mock Object
  • Fake Object

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

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

テスト対象
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を渡します。

Swiftでの例
テスト対象のメソッド
    func method(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
テストコード
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))
    }
}

Test Stub

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

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

Swiftでの例
テスト対象のメソッド
    func method() -> Int {
        let item = repository.get()
        return item?.value ?? 0
    }
テストコード
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())
    }
}

Test Spy

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

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

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

Swiftでの例
テスト対象のメソッド
    func method(value: Int) {
        let item = Item(value: value)
        repository.save(item)
    }
テストコード
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)
    }
}

Mock Object

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

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

Swiftでの例
テスト対象のメソッド
    func method(value: Int) {
        let item = Item(value: value)
        repository.save(item)
    }
テストコード
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.テスト成功)
    }
}

Fake Object

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

Swiftでの例
テスト対象のメソッド
    func method(value: Int) {
        let item = Item(value: value)
        repository.save(item)
    }
テストコード
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)
    }
}

まとめ

Test Double の分類をまとめるとこんな感じです。

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

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

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2