web-dev-qa-db-ja.com

単体テスト内のコードでバンドルリソースが見つからないのはなぜですか?

私が単体テストしているコードの中には、リソースファイルをロードする必要があるものがあります。次の行が含まれています。

NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];

アプリでは正常に実行されますが、単体テストフレームワークによって実行される場合はpathForResource:はnilを返します。つまり、foo.txt

foo.txtは、単体テストターゲットのバンドルリソースのコピービルドフェーズに含まれているのに、なぜファイルが見つからないのですか?

172
benzado

ユニットテストハーネスがコードを実行すると、ユニットテストバンドルは[〜#〜] not [〜#〜]メインバンドルになります。

アプリケーションではなくテストを実行している場合でも、アプリケーションバンドルは依然としてメインバンドルです。 (おそらく、これにより、テストしているコードが間違ったバンドルを検索するのを防ぐことができます。)したがって、ユニットテストバンドルにリソースファイルを追加すると、メインバンドルを検索しても検索されません。上記の行を次のように置き換えた場合:

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"foo" ofType:@"txt"];

その後、コードはユニットテストクラスが含まれているバンドルを検索し、すべてが正常に実行されます。

300
benzado

A Swift=実装:

Swift 2

let testBundle = NSBundle(forClass: self.dynamicType)
let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
XCTAssertNotNil(fileURL)

Swift 3、Swift 4

let testBundle = Bundle(for: type(of: self))
let filePath = testBundle.path(forResource: "imageName", ofType: "png")
XCTAssertNotNil(filePath)

バンドルは、構成のメインパスとテストパスを検出する方法を提供します。

@testable import Example

class ExampleTests: XCTestCase {

    func testExample() {
        let bundleMain = Bundle.main
        let bundleDoingTest = Bundle(for: type(of: self ))
        let bundleBeingTested = Bundle(identifier: "com.example.Example")!

        print("bundleMain.bundlePath : \(bundleMain.bundlePath)")
        // …/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents
        print("bundleDoingTest.bundlePath : \(bundleDoingTest.bundlePath)")
        // …/PATH/TO/Debug/ExampleTests.xctest
        print("bundleBeingTested.bundlePath : \(bundleBeingTested.bundlePath)")
        // …/PATH/TO/Debug/Example.app

        print("bundleMain = " + bundleMain.description) // Xcode Test Agent
        print("bundleDoingTest = " + bundleDoingTest.description) // Test Case Bundle
        print("bundleUnderTest = " + bundleBeingTested.description) // App Bundle

Xcode 6 | 7 | 8 | 9では、unit-test bundle pathDeveloper/Xcode/DerivedDataのようなものになります...

/Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      foo.txt

... Developer/CoreSimulator/Devices通常の(ユニットテストではない)バンドルパスとは別です

/Users/
  UserName/
    Library/
    Developer/
      CoreSimulator/
        Devices/
          _UUID_/
            data/
              Containers/
                Bundle/
                  Application/
                    _UUID_/
                      App.app/

また、単体テスト実行可能ファイルは、デフォルトでアプリケーションコードとリンクされていることに注意してください。ただし、単体テストコードには、テストバンドルのみにターゲットメンバーシップが含まれている必要があります。アプリケーションコードには、アプリケーションバンドルにターゲットメンバーシップのみが含まれている必要があります。実行時、単体テストのターゲットバンドルは 実行のためにアプリケーションバンドルに挿入されます

Swift Package Manager(SPM)4:

let testBundle = Bundle(for: type(of: self)) 
print("testBundle.bundlePath = \(testBundle.bundlePath) ")

注:デフォルトでは、コマンドラインSwift testMyProjectPackageTests.xctestテストバンドルを作成します。そして、Swift package generate-xcodeprojMyProjectTests.xctestテストバンドルを作成します。これらの異なるテストバンドルには、異なるパスがあります。 また、異なるテストバンドルには、いくつかの内部ディレクトリ構造とコンテンツの違いがある場合があります.

いずれの場合も、.bundlePathおよび.bundleURLは、macOSで現在実行されているテストバンドルのパスを返します。ただし、Bundleは現在Ubuntu Linuxには実装されていません。

また、コマンドラインSwift buildおよびSwift testは現在、リソースをコピーするメカニズムを提供していません。

ただし、多少の努力を払えば、macOS Xcode、macOSコマンドライン、Ubuntuコマンドライン環境のリソースでSwift Package Mangerを使用するためのプロセスを設定できます。1つの例を見つけることができます。ここ: 4.4'2 SW Dev SwiftリソースQrefを使用したパッケージマネージャー(SPM)

参照: Swift Package Manager)を使用した単体テストでのリソースの使用

Swift Package Manager(SPM)4.2

Swift Package Manager PackageDescription 4.2 は、 ローカル依存関係 のサポートを導入します。

ローカル依存関係は、パスを使用して直接参照できるディスク上のパッケージです。ローカル依存関係はルートパッケージでのみ許可され、パッケージグラフ内の同じ名前のすべての依存関係をオーバーライドします。

注:SPM 4.2では、次のようなことが可能になると予想されますが、まだテストされていません:

// Swift-tools-version:4.2
import PackageDescription

let package = Package(
    name: "MyPackageTestResources",
    dependencies: [
        .package(path: "../test-resources"),
    ],
    targets: [
        // ...
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage", "MyPackageTestResources"]
        ),
    ]
)
68
l --marc l

With Swift=Swift 3構文self.dynamicTypeは非推奨になりました。代わりにこれを使用してください

let testBundle = Bundle(for: type(of: self))
let fooTxtPath = testBundle.path(forResource: "foo", ofType: "txt")

または

let fooTxtURL = testBundle.url(forResource: "foo", withExtension: "txt")
13
MarkHim

リソースがテストターゲットに追加されたことを確認します。

enter image description here

4
mishimay

プロジェクトに複数のターゲットがある場合、Target Membershipで利用可能な異なるターゲット間にリソースを追加する必要があり、異なるTarget次の図に示す3つのステップとして

enter image description here

1
Sultan Ali

この一般テストのチェックボックスが設定されていることを確認する必要がありました this General Testing checkbox was set

0
drew..