一些代码,我是单元测试需要加载一个资源文件。它包含以下一行:

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

在应用程序中,它运行得很好,但当由单元测试框架运行时,pathForResource:返回nil,这意味着它无法找到foo.txt。

我已经确保foo.txt包含在单元测试目标的Copy Bundle Resources构建阶段,那么为什么它不能找到该文件呢?


当单元测试工具运行你的代码时,你的单元测试包不是主包。

即使您正在运行测试,而不是您的应用程序,您的应用程序包仍然是主要的包。(据推测,这可以防止您正在测试的代码搜索错误的包。)因此,如果您将一个资源文件添加到单元测试包中,那么在搜索主包时将找不到它。如果将上面的行替换为:

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

然后,代码将搜索单元测试类所在的包,一切正常。


一个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)

Bundle提供了发现配置的主路径和测试路径的方法:

@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/

还要注意,在默认情况下,单元测试可执行文件与应用程序代码链接在一起。然而,单元测试代码应该只在测试包中具有Target Membership。应用程序代码在应用程序包中应该只有Target Membership。在运行时,单元测试目标包被注入到应用程序包中以供执行。

Swift Package Manager (SPM)

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

注意:默认情况下,命令行swift测试将创建一个MyProjectPackageTests。Xctest测试包。并且,swift包generate-xcodeproj将创建一个MyProjectTests。Xctest测试包。这些不同的测试包有不同的路径。此外,不同的测试包可能有一些内部目录结构和内容差异。

在任何一种情况下,. bundlepath和. bundleurl将返回当前在macOS上运行的测试包的路径。然而,Bundle目前还没有为Ubuntu Linux实现。

此外,命令行swift构建和swift测试目前还没有提供复制资源的机制。

但是,通过一些努力,可以通过macOS Xcode、macOS命令行和Ubuntu命令行环境中的资源来设置使用Swift Package Manger的进程。一个例子可以在这里找到:004.4'2 SW Dev Swift Package Manager (SPM) With Resources Qref

请参见:使用Swift包管理器在单元测试中使用资源

Swift Package Manager (SwiftPM) 5.3

Swift 5.3包括软件包管理器资源SE-0271的发展建议,“状态:已实现(Swift 5.3)”。: -)

Resources aren't always intended for use by clients of the package; one use of resources might include test fixtures that are only needed by unit tests. Such resources would not be incorporated into clients of the package along with the library code, but would only be used while running the package's tests. Add a new resources parameter in target and testTarget APIs to allow declaring resource files explicitly. SwiftPM uses file system conventions for determining the set of source files that belongs to each target in a package: specifically, a target's source files are those that are located underneath the designated "target directory" for the target. By default this is a directory that has the same name as the target and is located in "Sources" (for a regular target) or "Tests" (for a test target), but this location can be customized in the package manifest.

// 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"),
      ]),

最新一期

Swift 5.3 SPM资源在测试中使用错误的bundle路径? Swift Package Manager -测试目标中的资源

Xcode

包。模块是由SwiftPM生成的(参见Build/BuildPlan.swift SwiftTargetBuildDescription generateResourceAccessor()),因此在Foundation中不存在。由Xcode构建时捆绑。

在Xcode中类似的方法是手动添加一个资源引用文件夹到模块中,添加一个Xcode构建阶段副本将资源放入一些*。bundle目录,并添加一个#ifdef Xcode编译器指令,以便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

确认资源已添加到测试目标。


与swift swift 3的语法自我。dynamicType已弃用,请改用此类型

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

or

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

如果你的项目中有多个目标,那么你需要在目标成员关系中不同的目标之间添加资源,你可能需要在不同的目标之间切换,如下图所示的3个步骤


我必须确保设置了General Testing复选框


就像我这样的人,在最初的帖子中错过了这一点:

确保foo。md包含在单元测试目标的Copy Bundle Resources构建阶段中


有一个找到文件的代码: 如何检查文件是否存在在Swift的文档目录?

我在一个测试中使用它,如下所示,测试我的文件是否被创建,并为进一步测试提供其位置。

        let fileFound = XCTestExpectation (description: "Checking for DB File Found")
    
    
    
    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
    let url = NSURL(fileURLWithPath: path)
    if let pathComponent = url.appendingPathComponent("filename.ext") {
        let filePath = pathComponent.path
        let fileManager = FileManager.default
        if fileManager.fileExists(atPath: filePath) {
            fileFound.fulfill()
            print("DB FILE AVAILABLE")
        } else {
            print("DB FILE NOT AVAILABLE")
        }
    } else {
        print("DB FILE PATH NOT AVAILABLE")
    }
    
    wait(for: [fileFound], timeout: 5)

这不是测试文件是否创建在正确的位置,而是测试文件是否创建了。