【问题标题】:NSURL to file path in test bundle with XCTestNSURL 到带有 XCTest 的测试包中的文件路径
【发布时间】:2013-10-18 23:59:37
【问题描述】:

我正在尝试使用 TDD 和新的 XCTest 框架编写一个 iOS 应用程序。我的一种方法从 Internet 检索文件(给定一个 NSURL 对象)并将其存储在用户的文档中。该方法的签名类似于:

- (void) fetchAndStoreImage:(NSURL *)imageUrl

我正在尝试以一种在没有互联网连接时不会失败的方式来编写此方法的测试。我的方法(取自之前的 question)是使用 NSURL 调用本地文件系统中的图像的方法。

创建启用了单元测试的新项目时,Tests 目录有一个名为“Supporting Files”的子目录。我想那是我的测试图像应该去的地方。我的问题是如何获取指向此目录中图像的 NSURL 对象,因为我不希望将测试图像与应用程序捆绑在一起。任何帮助表示赞赏。

【问题讨论】:

标签: ios tdd nsbundle xctest


【解决方案1】:

实际上,运行 UnitTest 时的[NSBundle mainBundle] 不是您的应用程序的路径,而是/Developer/usr/bin,所以这不起作用。

在单元测试中获取资源的方法在这里:OCUnit & NSBundle

简而言之,使用:

[[NSBundle bundleForClass:[self class]] resourcePath]

或者在你的情况下:

[[NSBundle bundleForClass:[self class]] resourceURL]

【讨论】:

  • @pshah 是的,我相信是的,虽然我自己没有尝试过。
  • 这非常有帮助。顺便说一句,在 Swift 中它是 NSBundle(forClass: self.dynamicType)
【解决方案2】:

Swift 5.3

注意:Swift 5.3 包含Package Manager Resources SE-0271 功能,可用于应用程序包和测试包资源。

资源并不总是供软件包的客户使用;资源的一种用途可能包括仅单元测试需要的测试夹具。此类资源不会与库代码一起合并到包的客户端中,而只会在运行包的测试时使用。

Swift 4、5:

let testBundle = Bundle(for: type(of: self))
guard var fileUrl = testBundle.url(forResource: "imageName", withExtension: "png") 
  else { fatalError() }

// path approach
guard let filePath = testBundle.path(forResource: "dataName", ofType: "csv")
  else { fatalError() }
let fileUrl = URL(fileURLWithPath: 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 URL 将位于 Developer/Xcode/DerivedData 中,类似于 ...

file:///Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      imageName.png

...与Developer/CoreSimulator/Devices URL 不同

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

还请注意,单元测试可执行文件默认情况下与应用程序代码链接。但是,单元测试代码应该只在测试包中具有 Target Membership。应用程序代码应仅在应用程序包中具有目标成员资格。在运行时,单元测试目标包是injected into the application bundle for execution

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 for Swift 4 中实现。

此外,Swift 4 命令行 swift buildswift test 不提供复制资源的机制。

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

【讨论】:

  • 谢谢!试试你的代码,应该是 URLForResource("imageName", withExtension: "png")
  • @Ciryon 该示例现在更正为withExtension。谢谢。 withType 将用于检索路径 pathForResource("imageName", ofType: "png") 而不是 URL。
  • 什么是self.dynamicType?
  • @Ramis self 引用封闭测试class SomeCaseTests: XCTestCase {..}。并且,.dynamicType 评估为类的运行时类型的值。请参阅 Apple 开发文档并滚动到 Dynamic Type Expression 部分。
  • @l--marcl 我正在使用 Xcode 7,但我仍然必须使用 self.dynamicType,否则我在尝试获取捆绑包时会出错。你确定你可以喂NSBundle(forClass:)self吗?
【解决方案3】:

只是为了追加正确答案,这是如何获取 UnitTests/Supporting Files 中文件的 filePath 的示例:

NSString *filePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"YourFileName.json"];
XCTAssertNotNil(filePath);

这可能对某人有用。

【讨论】:

    【解决方案4】:
    1. 您可以像在任何目标中一样引用捆绑文件。
    2. 检查文件是否在(您的测试目标的)复制捆绑资源构建阶段被复制
    3. 访问本地文件:

      NSURL*imageUrl=[[NSBundle mainBundle]URLForResource:@"imageName" withExtension:@"png"];
      

    您可以使用 https://github.com/travisjeffery/TRVSMonitor 进行异步访问并等待响应

    如果你在测试目标(2)中添加了:dataset1.json

    NSString *p=[[NSBundle mainBundle] pathForResource:@"dataset1" ofType:@"json"];
    NSLog(@"%@",p);
    

    2013-10-29 15:49:30.547 PlayerSample[13771:70b] WT(0): /Users/bpds/Library/Application Support/iPhone Simulator/7.0/Applications/7F78780B-684A-40E0-AA35-A3B5D8AA9DBD /PlayerSample.app.app/dataset1.json

    【讨论】:

    • 我试过了,好像没用。显然,测试包中的图像不会被复制到主包中。要走的路在 Ben 的回答中,你必须使用测试用例类中的 [NSBundle bundleForClass:[self class]] 来获取测试包。
    • 有趣的是,这似乎确实有效(至少对于 ios 模拟器目标)。您绝对需要确保将资源添加到测试目标的复制包资源构建阶段。
    【解决方案5】:

    Swift 版本基于接受的答案:

    let url = URL(fileURLWithPath: Bundle(for: type(of: self)).path(forResource: "my", ofType: "json") ?? "TODO: Proper checking for file")
    

    【讨论】:

      【解决方案6】:

      我面临的问题是应用程序代码试图访问主包以获取诸如 bundleIdentifier 之类的内容,并且由于主包不是我的单元测试项目,它会返回 nil。

      在 Swift 3.0.1 和 Objective-C 中都可以使用的解决方法是在 NSBundle 上创建一个 Objective-C 类别并将其包含在您的单元测试项目中。您不需要桥接头或任何东西。该类别将被加载,现在当您的应用程序代码请求主包时,您的类别将返回单元测试包。

      @interface NSBundle (MainBundle)
      
      +(NSBundle *)mainBundle;
      
      @end
      
      @implementation NSBundle (Main)
      
      #pragma clang diagnostic push
      #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
      +(NSBundle *)mainBundle
      {
          return [NSBundle bundleForClass:[SomeUnitTest class]];
      }
      #pragma clang diagnostic pop
      
      @end
      

      【讨论】:

        【解决方案7】:

        这是它的 Swift 版本、Xcode 7、iOS 9 等。

        let testBundle = NSBundle(forClass: self.dynamicType)
        let path = testBundle.pathForResource("someImage", ofType: "jpg")
        XCTAssertNotNil(path)
        

        注意:someImage.jpg 必须包含在您的测试目标中。


        编辑:Swift 5

        let testBundle = Bundle(for: type(of: self))
        let path = testBundle.path(forResource: "someImage", ofType: "jpg")
        XCTAssertNotNil(path)
        

        【讨论】:

          【解决方案8】:

          斯威夫特 5

          let testBundle = Bundle(for: self)
          

          使用let testBundle = Bundle(for: type(of: self))(在上面的一些答案中找到)将无法编译,而是始终对我产生 Segmentation fault: 11 错误。

          【讨论】:

            猜你喜欢
            • 2011-10-02
            • 2014-02-15
            • 1970-01-01
            • 1970-01-01
            • 2014-06-22
            • 2014-05-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多