Compilation Flagでテスト容易なコードをつくる
SwiftでiOSアプリなどを開発していると「リリース時はdefaultのシングルトンインスタンスを使って欲しいが、テスト時はテストダブルの依存性を注入したい」とか「リリース時は内部プロパティを公開したくないが、テストではアサーションを書くのに内部プロパティにアクセス出来て欲しい」と思うことありませんか?
そのような場合にはCompilation Flagを使うと便利なことを発見しました。この記事のサンプルではリリースビルドで弾ければ良いと考えて#if DEBUGを使いますが、独自フラグ(例えば #if TEST など)を定義する場合は、適時読み替えてください。
依存性注入の場合
例えば、アプリのモデルオブジェクトをファイルに保存する処理を仲介するMyFileCoordinatorがあるとして、実際のファイルハンドルを行うFileManagerオブジェクトを保持しています。このとき #if DEBUG を使えば、デバッグ時のみイニシャライザのアクセスコントロールをinternalに開放しておくことができます。
class MyFileCoordinator {
static let `shared` = MyFileCoordinator(fileManager: .default)
let fileManager: FileManager
#if DEBUG
init(fileManager: FileManager) {
self.fileManager = fileManager
}
#else
private init(fileManager:FileManager {
self.fileManager = fileManager
}
#endif
}
こうすると、リリースビルドではシングルトンの利用を強制しつつ、XCTestなどのフレームワーク内では、internal init(fileManager:)にアクセスすることが可能なので、FileManagerをサブクラスしたStubFileManager: FileManagerなどのテストダブルを注入でき、テストコードを書くのが容易になります。
内部プロパティを公開する場合
ちょっと特殊な仕様を表現する必要があって、内部プロパティを公開せずに、実装したAPIからのみアクセスして欲しいときがあります。例えば、MyObject内に隠蔽したいinternalArrayというプロパティがあるとき、#if DEBUGを使えば、デバッグ時のみinternalArrayのアクセスコントロールをinternalに開放しておくことができます。
struct MyObject {
#if DEBUG
private(set) internalArray: [String] = []
#else
private internalArray: [String] = []
#endif
mutating func add(_ member: String) { internalArray.append(member) }
}
こうすると、リリースビルドでは内部プロパティの存在を隠蔽しつつ、XCTestなどのフレームワーク内ではprivate(set) internalArray: [String]にアクセスすることが可能なので、XCTAssetEqual(myObject.internalArray.count, 3)など操作後の内部状態をチェックでき、テストコードを書くのが容易になります。
Discussion