Dictionaryから値を取得する、Arrayのn番目の値を取得して何かする、計算するなど
privateなfuncを使用することは多いですよね。
しかし、そのprivateなfuncはロジックであることが多いです。つまりテストしたいですね
しかし、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で定義するのが簡単で良いと思います