ユニットテスト内のコードがバンドルリソースを見つけられないのはなぜですか?


184

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

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

アプリでは問題なく実行されますが、単体テストフレームワークで実行pathForResource:するとnilが返され、検索できませんでしたfoo.txt

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

回答:


316

ユニットテストハーネスは、あなたのコードを実行すると、あなたのユニットテストバンドルはありませメインバンドルで。

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

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

次に、コードはユニットテストクラスが含まれているバンドルを検索し、すべて正常に動作します。


私にはうまくいきません。それでもビルドバンドルであり、テストバンドルではありません。
Chris

1
@Chrisサンプル行selfでは、テストケースクラスではなく、メインバンドルのクラスを参照していると想定しています。[self class]メインバンドルの任意のクラスに置き換えます。私の例を編集します。
ベンザド2012

@benzadoバンドルはまだ同じ(ビルド)で、正しいと思います。私が自分自身またはAppDelegateを使用している場合、両方がメインバンドルに配置されているためです。メインターゲットのビルドフェーズを確認すると、両方のファイルが含まれています。しかし、実行時にメインバンドルとテストバンドルの間で何を変更したいか。バンドルが必要なコードはメインバンドルにあります。以下の問題があります。PNGファイルを読み込んでいます。通常、ユーザーがサーバーからファイルをダウンロードするため、このファイルはメインバンドルにありません。しかし、テストでは、メインバンドルにコピーせずに、テストバンドルのファイルを使用したいと思います。
クリス

2
@Chris前回の編集を間違えたため、もう一度回答を編集しました。テスト時には、App Bundleがメインバンドルのままです。単体テストバンドルにあるリソースファイルをロードする場合は、単体テストバンドルbundleForClass:のクラスで使用する必要があります。ユニットテストコードでファイルのパスを取得し、パス文字列を他のコードに渡す必要があります。
ベンザド

これは機能しますが、実行デプロイとテストデプロイをどのように区別できますか?それがテストであるという事実に基づいて、メインバンドルのクラスにテストバンドルのリソースが必要です。通常の「実行」の場合、テストバンドルではなく、メインバンドルのリソースが必要です。何か案が?
Chris

80

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では、単体テストバンドルパスDeveloper/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-xcodeprojが作成されますMyProjectTests.xctest。これらの異なるテストバンドルには異なるパスがありますまた、さまざまなテストバンドルには、内部ディレクトリ構造とコンテンツの違いがある場合があります

どちらの場合でも、 .bundlePath.bundleURLは現在macOSで実行されているテストバンドルのパスを返します。ただし、BundleUbuntu Linuxでは現在実装されていません。

また、コマンドラインswift buildswift testは現在、リソースをコピーするためのメカニズムがありません。

ただし、多少の努力を払えば、macOS Xcode、macOSコマンドライン、Ubuntuコマンドライン環境のリソースでSwift Package Mangerを使用するためのプロセスを設定することが可能です。一例はここにあります: 004.4'2 SW Dev Swift Package Manager(SPM)With Resources Qref

以下も参照してください。 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"]
        ),
    ]
)

1
Swift 4の場合も、Bundle(for:type(of:self))を使用できます
Rocket Garden

14

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")

4

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

ここに画像の説明を入力してください


2
テストバンドルにリソースを追加すると、テスト結果がほぼ無効になります。結局のところ、リソースは簡単にテストターゲットには存在してもアプリターゲットには存在しない可能性があり、テストはすべて成功しますが、アプリは突然爆発します。
dgatwood 2016

1

プロジェクトに複数のターゲットがある場合は、ターゲットメンバーシップで使用可能な異なるターゲット間でリソースを追加する必要があり、異なる間で切り替える必要がある場合があります。ます。また、下の図に示す3つのステップとしてターゲットます。

ここに画像の説明を入力してください


0

このGeneral Testingチェックボックスが設定されていることを確認する必要がありました このGeneral Testingチェックボックスが設定されました

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.