test
Swift

private funcをテストしたいとき

Dictionaryから値を取得する、Arrayのn番目の値を取得して何かする、計算するなど
privateなfuncを使用することは多いですよね。

しかし、そのprivateなfuncはロジックであることが多いです。つまりテストしたいですね:innocent:
しかし、privateにするとTestから呼び出すことができないので、テストができません。

privateなfunc

struct User {

    func save(dictionary: [String: String]) {
        guard let name = getname(from: dictionary) else {
            return
        }
        UserDefaults.standard.set(name, forKey: "key")
    }

    private func getName(from dictionary: [String: String]) -> String? {
        return dictionary["name"]
    }

}

例えば、このような例ですね。
外からはsave()を呼べればよい。そして、getName()はテストしたい。

インナークラスに隠す

インナークラスを作成して、そこにprivateなfuncを定義します

privateなfuncのprotocolを作る

protocol UserPrivateProtocol {
    static func getName(from dictionary: [String: String]) -> String?
}

extension UserPrivateProtocol {

    static func getName(from dictionary: [String: String]) -> String? {
        return dictionary["name"]
    }

}

まずは、privateにしたいfuncをprotocolに実装します。
このprotocolはinternalなので準拠したクラスを作れば、getName()は使えてしまうのですが、それは目をつむります笑

インナークラスをprotocolに準拠させる

struct User {

    private struct Private: UserPrivateProtocol {
    }

    func save(dictionary: [String: String]) {
        guard let name = Private.getName(from: dictionary) else {
            return
        }
        UserDefaults.standard.set(name, forKey: "key")
    }

}

インナークラスをprivateにすることで外から触ることができなくなります。
しかし、User内ではちゃんと使えます。

Testする

let dictionary = ["name": "tarou"]

struct UserPrivateMock: UserPrivateProtocol {

}

XCTAssertEqual(dictionary["name"], UserPrivateMock.getName(from: dictionary))

テストするときは、privateなfunc用に作ったprotocolに準拠したMockクラスを作成します。
そのMockクラスを使用することでテストが可能になります。

課題

protocolのdefault実装を利用して、Mockを作ってテストするのですが、getName()の実装がdefaultのままという前提のテストになってしまいます。

インナークラス内にprivateにしたいfuncを定義

インナークラス作成

struct User {

    struct Private {
        static func getName(from dictionary: [String: String]) -> String? {
            return dictionary["name"]
        }
    }

    func save(dictionary: [String: String]) {
        guard let name = Private.getName(from: dictionary) else {
            return
        }
        UserDefaults.standard.set(name, forKey: "key")
    }

}

インナークラスにprivateにしたいfuncを定義します。
User.Private.getName()でアクセスできてしまうのですが、それは目をつむりましょう笑

Test

let dictionary = ["name": "tarou"]

XCTAssertEqual(dictionary["name"], User.Private.getName(from: dictionary))

これでテストができます。

まとめ

2つの方法を思いついたので書きましたが、特に問題がないならinternalで定義するのが簡単で良いと思います:relaxed: