198

単体テストを行っている一部のコードでは、リソース ファイルを読み込む必要があります。次の行が含まれています。

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

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

ユニット テスト ターゲットのCopy Bundle Resourcesfoo.txtビルド フェーズに含まれていることを確認しましたが、ファイルが見つからないのはなぜですか?

4

6 に答える 6

332

単体テスト ハーネスがコードを実行するとき、単体テスト バンドルはメイン バンドルではありません。

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

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

次に、コードは単体テスト クラスが含まれるバンドルを検索し、すべて問題ありません。

于 2009-12-10T07:40:25.687 に答える
104

Swift の実装:

スイフト2

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

スイフト 3、スイフト 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/

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

スウィフト パッケージ マネージャー (SPM) 4:

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

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

いずれの場合も、.bundlePathand.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 を使用して単体テストでリソースを使用する

スウィフト パッケージ マネージャー (SwiftPM) 5.3

Swift 5.3 には、「ステータス:実装済み (Swift 5.3) 」の Package Manager Resources SE-0271進化提案が含まれています。:-)

リソースは、パッケージのクライアントによる使用を常に意図しているわけではありません。リソースの 1 つの用途には、単体テストでのみ必要なテスト フィクスチャが含まれる場合があります。このようなリソースは、ライブラリ コードと共にパッケージのクライアントに組み込まれることはなく、パッケージのテストの実行中にのみ使用されます。

  • resourcesAPI に新しいパラメーターをtarget追加して、testTargetリソース ファイルを明示的に宣言できるようにします。

SwiftPM は、ファイル システム規則を使用して、パッケージ内の各ターゲットに属するソース ファイルのセットを決定します。具体的には、ターゲットのソース ファイルは、ターゲットに指定された「ターゲット ディレクトリ」の下にあるファイルです。デフォルトでは、これはターゲットと同じ名前のディレクトリで、"Sources" (通常のターゲットの場合) または "Tests" (テスト ターゲットの場合) にありますが、この場所はパッケージ マニフェストでカスタマイズできます。

// Get path to DefaultSettings.plist file.
let path = Bundle.module.path(forResource: "DefaultSettings", ofType: "plist")

// Load an image that can be in an asset archive in a bundle.
let image = UIImage(named: "MyIcon", in: Bundle.module, compatibleWith: UITraitCollection(userInterfaceStyle: .dark))

// Find a vertex function in a compiled Metal shader library.
let shader = try mtlDevice.makeDefaultLibrary(bundle: Bundle.module).makeFunction(name: "vertexShader")

// Load a texture.
let texture = MTKTextureLoader(device: mtlDevice).newTexture(name: "Grass", scaleFactor: 1.0, bundle: Bundle.module, options: options)

// swift-tools-version:5.3
import PackageDescription

  targets: [
    .target(
      name: "CLIQuickstartLib",
      dependencies: [],
      resources: [
        // Apply platform-specific rules.
        // For example, images might be optimized per specific platform rule.
        // If path is a directory, the rule is applied recursively.
        // By default, a file will be copied if no rule applies.
        .process("Resources"),
      ]),
    .testTarget(
      name: "CLIQuickstartLibTests",
      dependencies: [],
      resources: [
        // Copy directories as-is. 
        // Use to retain directory structure.
        // Will be at top level in bundle.
        .copy("Resources"),
      ]),

現在の問題

Xcode

Bundle.moduleSwiftPM ( Build/BuildPlan.swift SwiftTargetBuildDescription generateResourceAccessor()を参照) によって生成されるため、Xcode によってビルドされた場合、 Foundation.Bundleには存在しません。

Xcode での同等のアプローチは、Resources参照フォルダーをモジュールに手動で追加し、Xcode ビルド フェーズを追加してディレクトリにcopy配置し、Xcode ビルドがリソースを操作するためのコンパイラ ディレクティブを追加することです。Resource*.bundle#ifdef Xcode

#if Xcode 
extension Foundation.Bundle {
  
  /// Returns resource bundle as a `Bundle`.
  /// Requires Xcode copy phase to locate files into `*.bundle`
  /// or `ExecutableNameTests.bundle` for test resources
  static var module: Bundle = {
    var thisModuleName = "CLIQuickstartLib"
    var url = Bundle.main.bundleURL
    
    for bundle in Bundle.allBundles 
      where bundle.bundlePath.hasSuffix(".xctest") {
      url = bundle.bundleURL.deletingLastPathComponent()
      thisModuleName = thisModuleName.appending("Tests")
    }
    
    url = url.appendingPathComponent("\(thisModuleName).bundle")
    
    guard let bundle = Bundle(url: url) else {
      fatalError("Bundle.module could not load: \(url.path)")
    }
    
    return bundle
  }()
  
  /// Directory containing resource bundle
  static var moduleDir: URL = {
    var url = Bundle.main.bundleURL
    for bundle in Bundle.allBundles 
      where bundle.bundlePath.hasSuffix(".xctest") {
      // remove 'ExecutableNameTests.xctest' path component
      url = bundle.bundleURL.deletingLastPathComponent()
    }
    return url
  }()
  
}
#endif
于 2015-06-06T21:50:22.957 に答える
16

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")
于 2016-09-21T08:40:53.367 に答える
5

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

ここに画像の説明を入力

于 2016-09-09T04:10:51.170 に答える