post Image
iOSアプリでMockを使ってUnitTestを書く

VIPERやClean Architectureなどでは当然されていますが、UTをするためにはProtocolを用いて各レイヤー間の依存度を下げテストをしやすくする必要があります。

テストしにくい(依存度が高い)コード

class BookmarkViewModel {
    let model = BookmarkModel()
    var bookmarks = [Bookmark]()

    // DBからブックマーク一覧を取得
    func loadBookmarkList() {
        bookmarks = model.load()
    }

    // DBからi番目のブックマークを削除
    func deleteBookmark(at index: Int) {
        model.delete(at: index)
    }
}

このコードではBookmarkViewModelBookmarkModelに依存しているため、
BookmarkViewModelのテストをするときは以下のようにViewModelの範囲を超えてModelレイヤーのことまで意識しないといけなくなってしまいます。

class BookmarkViewModelTests: XCTestCase {
    var vm = BookmarkViewModel()
    func testLoadBookmarkList() {
        let model = BookmarkModel()
        // 例:BookmarkModelが操作するDBを意識してテスト用に2件データを追加
        model.insert(Bookmark(id: "bookmark1"))
        model.insert(Bookmark(id: "bookmark2"))

        vm.loadBookmarkList()
        // 上で2件追加したので取得も2件でbookmarks変数に2件あるはず
        XCTAssertEqual(vm.bookmarks.count, 2)
    }
}

このような単純なModelならそこまでテストに苦労はしませんが、複雑になってくるにつれテストが行いにくくなってきます。そのような状況になってもテストをするために、Protocolを用いてViewModelからModelの実態を意識させないようにします。

テストしやすい(依存度が低い)コード

protocol BookmarkModelProtocol {
    func load() -> [Bookmark]
    func delete(at index: Int)
}

class BookmarkModel: BookmarkModelProtocol {
    // 本来の実装すべきロジック
}

class BookmarkViewModel {
    let model: BookmarkModelProtocol
    var bookmarks = [Bookmark]()

    init(model: BookmarkModelProtocol = BookmarkModel()) {
       self.model = model
    }

    func loadBookmarkList() {
        bookmarks = model.load()
    }

    func deleteBookmark(at index: Int) {
        model.delete(at: index)
    }
}

このコードになるとBookmarkViewModelBookmarkModelProtocolにしか依存してなくinit経由で自由にオブジェクトを差し替えることができます。
そのためテストでは以下のように通常の時とは違う挙動できるようになります。

class BookmarkModelMock: BookmarkModelProtocol {
    func load() -> [Bookmark] {
        // 2件返す
        return [Bookmark(id: "bookmark1"), Bookmark(id: "bookmark2")]
    }

    func delete(at index: Int) { // なにもしない }
}

class BookmarkViewModelTests: XCTestCase {
    var vm = BookmarkViewModel(model: BookmarkModelMock())
    func testLoadBookmarkList() {
        vm.loadBookmarkList()
        // mockでは2件返すので、それをちゃんと変数に入れてるかのチェックだけ気にする
        XCTAssertEqual(vm.bookmarks.count, 2)
    }
}

このようにMockを使うことで他レイヤーのことを気にせずTest対象クラスに閉じた挙動をテストできます。
また他レイヤーに影響を与えないので、最後にDBの全削除などの後処理も不要になり影響範囲が小さくなります。

ただ現状のMockだと固定値を返すだけで、返却値がないメソッドは動作の確認をできないので、以下のように変更を加えテストできるようにします。


// インスタンス変数を保持するのでテストケース毎の初期化が必要
class BookmarkModelMock: BookmarkModelProtocol {
    var isLoadCalled = false
    var loadResult = [Bookmark]()
    func load() -> [Bookmark] {
        isLoadCalled = true
        return loadResult
    }

    var isDeleteCalled = false
    var deleteParam: Int?
    func delete(at index: Int) { 
        isDeleteCalled = true
        deleteParam = index
    }
}

class BookmarkViewModelTests: XCTestCase {
    func testLoadBookmarkList() {
        let mock = BookmarkModelMock()
        // テストパターンに応じて外部から返却値を設定(今回は2件返す)
        mock.loadResult = [Bookmark(id: "bookmark1"), Bookmark(id: "bookmark2")]

        let vm = BookmarkViewModel(model: mock)
        vm.loadBookmarkList()
        // mockでは2件返すので、それをちゃんと変数に入れてるかチェック
        XCTAssertEqual(vm.bookmarks.count, 2)

        mock.loadResult.removeAll()
        vm.loadBookmarkList()
        // modelの値が変わったらちゃんと反映されるかチェック
        XCTAssertEqual(vm.bookmarks.count, 0)
    }

    func testDelete() {
        let mock = BookmarkModelMock()
        let vm = BookmarkViewModel(model: mock)
        // まだ呼ばれないのでfalse
        XCTAssertFalse(mock.isDeleteCalled)

        vm.remove(at: 0)
        // BookmarkViewModelから正しくModelのメソッドが呼ばれているかチェック
        XCTAssertTrue(mock.isDeleteCalled)
        XCTAssertEqual(mock.deleteParam!, 0)
    }
}

このようにすることでレイヤーまたぎの処理なども影響範囲を小さくしつつ、正しい振る舞いができているかテストすることができます。

ちなみにAndroidでは、このようなことができるmockitoというライブラリがありますが、iOSではなかったのでこの様な形で実装しました。


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。