NSURL到带有XCTest的测试包中的文件路径


77

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

- (void) fetchAndStoreImage:(NSURL *)imageUrl

我正在尝试为此方法编写测试,以确保在没有互联网连接的情况下它不会失败。我的方法(来自上一个问题)是使用NSURL调用该方法来对本地文件系统中的图像进行调用。

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


此答案无需更改代码即可解决该问题stackoverflow.com/a/24330617/468868
Carlos Ricardo 2014年

Answers:


131

实际上,[NSBundle mainBundle]当运行UnitTest时,它不是您应用程序的路径,而是/ Developer / usr / bin,因此将无法使用。

在单元测试中获取资源的方法如下:OCUnit和NSBundle

简而言之,使用:

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

或者您的情况:

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

@pshah是的,我相信,尽管我自己还没有尝试过。
Ben Clayton 2014年

21
这非常有帮助。顺便说一句,在Swift中NSBundle(forClass: self.dynamicType)
Bill

50

迅捷5.3

注意:Swift 5.3包含Package Manager Resources SE-0271功能,可与应用程序捆绑包和测试捆绑包资源一起使用。

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

斯威夫特4、5:

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

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

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

...与Developer/CoreSimulator/DevicesURL分开

file:///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 test创建MyProjectPackageTests.xctest测试包。并且,swift package generate-xcodeproj将创建一个MyProjectTests.xctest测试包。这些不同的测试包具有不同的路径同样,不同的测试包可能具有一些内部目录结构和内容差异

无论哪种情况,.bundlePathand.bundleURL都会返回当前在macOS上运行的测试包的路径。但是,Bundle当前尚未为Ubuntu实现。

另外,命令行swift buildswift test当前不提供复制资源的机制。

但是,只要付出一些努力,就有可能在macOS Xcode,macOS命令行和Ubuntu命令行环境中通过资源来设置使用Swift软件包管理器的过程。可以在此处找到一个示例:004.4'2具有资源Qref的SW Dev Swift软件包管理器(SPM)


谢谢!尝试您的代码,应该为URLForResource(“ imageName”,withExtension:“ png”)
Ciryon

@Ciryon现在,示例已更正为withExtension。谢谢。 withType将用于检索路径pathForResource("imageName", ofType: "png")而不是URL。
l --marc l

什么是self.dynamicType?
拉米斯2015年

@Ramisself引用了封闭测试class SomeCaseTests: XCTestCase {..}。并且,.dynamicType求值为类的运行时类型的值。请参阅Apple Dev文档,然后滚动到“动态类型表达式”部分。
l --marc l 2015年

@ l--marcl我正在使用Xcode 7,但仍必须使用,self.dynamicType否则在尝试获取捆绑包时会出错。您确定您可以喂食NSBundle(forClass:) self吗?
Ziewvater,2015年

14

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

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

这可能对某人有用。


3
  1. 您可以像在任何目标中一样引用捆绑文件。
  2. 在(测试目标的)“副本捆绑资源”构建阶段检查文件是否已复制
  3. 要访问本地文件:

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

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

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

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]]获取测试包。
2013年

1
有趣的是,这似乎确实有效(至少对于ios模拟器目标而言。)您绝对需要确保将资源添加到测试目标的副本捆绑包资源构建阶段。
voidref

2

Swift 根据接受的答案的版本:

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

1

我面临的问题是,应用程序代码正在尝试访问诸如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

0

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

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)

0

迅捷5

let testBundle = Bundle(for: self)

使用let testBundle = Bundle(for: type(of: self))在上述某些答案中找到的,将不会编译,而是始终产生Segmentation Fault错误:对我来说是11

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.