Xcodeプロジェクト内で使っているStoryboardやXibが全てインスタンス化可能な事をテストする

  • 22
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

StoryboardやXibの初期化はStringでファイル名を渡す形なので、注意していないと実行時にクラッシュしてしまう事があります。渡す名前をタイポしているならまだ気づきやすいですが、たとえば.xcodeprojをマージしようとしてコンフリクトの解決に失敗して、普段あまり開かない画面のIBファイルの参照を消してしまったりするとクラッシュに気づかずリリースしてしまう可能性もあります。

これどうにかできないかなーと考えていたけど、単純に以下のようなテストを書けば解決できそうです。
内容はプロジェクトディレクトリ下から.storyboard、.xibファイルの一覧を取得し、全てのファイル名でUIStoryboard・UINibインスタンスを生成するという単純なもの。万が一、ファイル上は存在するけど.xcodeprojから参照されてないものがあるとfailします。

import XCTest

class IBInstantiationTests: XCTestCase {

    let fileManager = NSFileManager()
    // ソースディレクトリのパス(Info.plist内に記述)
    let sourceDirectory = NSBundle(forClass: IBInstantiationTests.self).infoDictionary!["Source Directory"] as! String

    var xibs: [String] {
        return fileManager.enumeratorAtPath(sourceDirectory)!
            .filter({ $0.hasSuffix(".xib") })
            .flatMap({ NSURL(string: $0 as! String)?.URLByDeletingPathExtension?.lastPathComponent })
    }

    var storyboards: [String] {
        return fileManager.enumeratorAtPath(sourceDirectory)!
            .filter({ $0.hasSuffix(".storyboard") })
            .flatMap({ NSURL(string: $0 as! String)?.URLByDeletingPathExtension?.lastPathComponent })
    }

    func testInstantiate() {
        xibs.forEach { XCTAssertNotNil(UINib(nibName: $0, bundle: nil)) }
        storyboards.forEach { XCTAssertNotNil(UIStoryboard(name: $0, bundle: nil)) }
    }
}

プロジェクトディレクトリのパスは、以下のようにTest TargetのInfo.plistに書いたものをNSBundleのinfoDictionaryで取ってきてます。単体テスト等のコードからは直接ソースディレクトリのパスが分からないため、無理やりだけどplistでXcodeの環境変数を展開し、それをコード側から参照するという形。

Kobito.Mc2h2c.png

自分は普段.xcodeprojのコンフリクト対策でsimonwagner/mergepbxを使っているんですが、たまーにこいつがうまくコンフリクトを解決できず、新しく追加したファイルの参照が消えてしまったりする事がありました。このテストをCIで走らせておくようにすればマージ後も心配せずに済みそうです。