xcode 为什么单元测试中的代码找不到bundle资源?

zd287kbt  于 2023-01-14  发布在  其他
关注(0)|答案(8)|浏览(190)

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

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

在应用程序中,它运行得很好,但是当由单元测试框架运行时,pathForResource:返回nil,这意味着它无法定位foo.txt
我已经确保foo.txt包含在单元测试目标的Copy Bundle Resources构建阶段,那么为什么它找不到该文件呢?

mnowg1ta

mnowg1ta1#

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

mcdcgff02#

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

jchrr9hc

jchrr9hc3#

如果项目中有多个目标,则需要在目标成员资格中可用的不同目标之间添加资源,并且可能需要在不同的目标之间切换,如下图所示的3个步骤

ljsrvy3e

ljsrvy3e4#

我必须确保将此“General Testing”复选框设置为

xtfmy6hx

xtfmy6hx5#

只是对于像我这样的人来说,在最初的帖子中忽略了这一点:

确保foo.md包含在单元测试目标的复制捆绑资源构建阶段

lsmepo6l

lsmepo6l6#

有一个代码可以在以下位置找到文件:How to check if a file exists in the Documents directory in 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)

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

3mpgtkmj

3mpgtkmj7#

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

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

然后,您的代码将搜索单元测试类所在的包,一切都会很好。

busg9geu

busg9geu8#

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/
  • 另请注意,默认情况下,单元测试可执行文件与应用程序代码链接。但是,单元测试代码应仅在测试捆绑包中具有目标成员资格。应用程序代码应仅在应用程序捆绑包中具有目标成员资格。在运行时,单元测试目标捆绑包将注入到应用程序捆绑包中以供执行。*
    • Swift软件包管理器(SPM)4:**
let testBundle = Bundle(for: type(of: self)) 
print("testBundle.bundlePath = \(testBundle.bundlePath) ")

注:默认情况下,命令行swift test将创建一个MyProjectPackageTests.xctest测试包。而swift package generate-xcodeproj将创建一个MyProjectTests.xctest测试包。这些不同的测试包具有不同的路径。* 此外,不同的测试包可能具有某些内部目录结构和内容差异。*
在任何一种情况下,.bundlePath.bundleURL都将返回当前在macOS上运行的测试包的路径。但是,Bundle目前尚未在Ubuntu 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在单元测试中使用资源

资源并不总是为包的客户端所使用;资源的一个用途可能包括仅单元测试需要的测试固定装置。这样的资源不会与库代码一起被合并到包的客户端中,而只会在运行包的测试时使用。

  • targettestTarget API中添加新的resources参数,以允许显式声明资源文件。

SwiftPM使用文件系统约定来确定属于软件包中每个目标的源文件集:具体来说,目标的源文件是那些位于为目标指定的"目标目录"下的文件。默认情况下,该目录与目标同名,位于"源"(对于常规目标)或"测试"(对于测试目标)中,但可以在包清单中定制该位置。

// 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中的一种类似方法是手动将Resources引用文件夹添加到模块,添加Xcode构建阶段copy以将Resource放入某个*.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

相关问题