テストダブルとは
ユニットテストで、テスト対象が依存しているオブジェクトを置き換える代用品のことです。
テストダブルは以下の5つに分類されます。
- Dummy Object
- Test Stub
- Test Spy
- Mock Object
- Fake Object
それぞれの違いをSwiftで簡単な例を示しながら説明します。
例では Repository
に依存する Subject
の method()
のテストを考えます。
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
違いを意識して呼び分けるようにしましょう!
参考