うーん、公開したいわけじゃないけど細かく意味のある単位に分割してテストしたいって普通にあると思うんだけどなぁ。ロジックが複雑な場合細かく分割してテストした方が効率的なことが多いし。全部パッケージプライベートにするのもパッケージ内での可視性がガバガバになるし。 https://t.co/T7JgQwQmlm
— koher (@koher) 2017年11月11日
すこぶる同意。
ということで考えた。
型に、privateだけどtestしたいメンバがあるパターン
struct Hoge {
init(x: Int) { self.x = x }
/// privateだけどTestしたいメンバ
fileprivate let x: Int
fileprivate func doSomething(with string: String) -> String { return "\(string) \(x)" }
}
Targeted Extensionを使うとよさそう。
public protocol PrivatePublicatable {
associatedtype Publicater
var `private`: Publicater { get }
}
public final class Private<Base> {
public let base: Base
public init(_ base: Base) { self.base = base }
}
public extension PrivatePublicatable {
var `private`: Private<Self> { return Private(self) }
}
↑こういうのを用意して…
Hoge.swift
内にextensionを書く。
extension Hoge: PrivatePublicatable {}
public extension Private where Base == Hoge {
var x: Int { return base.x }
func doSomething(with string: String) -> String {
return base.doSomething(with: string)
}
}
すると…
let hoge = Hoge(x: 1)
hoge.private.x // 1
hoge.private.doSomething(with: "ほげ") // ほげ 1
外からアクセスできるけどprivateっぽさを明示できる。
そもそも型自身がprivateなパターン
private struct Fuga {
let x: Int
let y: Int
func doSomething(with string: String) -> String { return "\(string) \(x) \(y)" }
}
型自体が外部から見えないので、アダプタとなる型を用意しないとどうしようもない。
/// Fugaをprivateに保つために作るTest用ラッパー。Fugaと同じファイル内に書く
final class FugaTester {
private let base: Fuga
init(args: (x: Int, y: Int)) {
base = Fuga(x: args.x, y: args.y)
}
var x: Int { return base.x }
var y: Int { return base.y }
func doSomething(with string: String) -> String {
return base.doSomething(with: string)
}
}
let tester = FugaTester(args: (x: 1, y: 2))
tester.x // 1
tester.y // 2
tester.doSomething(with: "ふが") // "ふが 1 2"
辛い…本当にこの道しかないんでしょうか…
ないとしても、ボイラープレートの山なので、Testerは自動生成したさありますね。
(すごい要望があればXcode Source Editor Extensionを作るのもやぶさかではない)
補足
はい。internalでも気にならないものであれば、そうしたほうがシンプルですね。しかし本来見えてほしくないものがプロダクトコードで補完に出てしまうのもノイズになるので、「テストコード以外では見えてほしくない」という意図を現状の言語仕様の上で表現するならこうなるかなと考えました。
— takasek (@takasek) 2017年11月11日