在Xcode单元测试中加载文件

72

我有一个Xcode 5单元测试项目和一些相关的测试XML文件。我尝试了很多方法,但似乎无法加载XML文件。

我尝试了以下方法,但没有成功:

NSData* nsData = [NSData dataWithContentsOfFile:@"TestResource/TestData.xml"];

NSString* fileString = [NSString stringWithContentsOfFile:@"TestData.xml" encoding:NSUTF8StringEncoding error:&error];

如果我尝试使用[NSBundle allBundles]预览所有的bundle,为什么单元测试的bundle不会出现在列表中?

我尝试创建一个独立的资源bundle,虽然它确实被构建和部署了,但是我似乎无法在程序中找到它。

我做错了什么?


嘿,你把文件放在哪里,以便通过路由“TestResource/TestData.xml”访问它? - Bhargav
8个回答

121

运行测试时,应用程序包仍然是主要的包。您需要使用单元测试包。

Objective C:

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"TestData" ofType:@"xml"];
NSData *xmlData = [NSData dataWithContentsOfFile:path];

Swift 2:

let bundle = NSBundle(forClass: self.dynamicType)
let path = bundle.pathForResource("TestData", ofType: "xml")!
let xmlData = NSData(contentsOfFile: path)

Swift 3及以上版本:

let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: "TestData", ofType: "xml")!
let xmlData = NSData(contentsOfFile: path) 

2
保持最新更新 - Mitchell Currie
"[self class]" 就是解决方法。"[NSBundle MainBundle]" 不适用于此处。这解决了我的问题。谢谢! - abhimuralidharan
2
Bundle(for: Self.self) - abc123

16

使用 Swift 3,self.dynamicType 的语法已被弃用,请改为使用此代码。

let testBundle = Bundle(for: type(of: self))
guard let ressourceURL = testBundle.url(forResource: "TestData", ofType: "xml") else {
    // file does not exist
    return
}
do {
    let ressourceData = try Data(contentsOf: ressourceURL)
} catch let error {
    // some error occurred when reading the file
}
或者。
guard let ressourceURL = testBundle.url(forResource: "TestData", withExtension: "xml")

1
这个比最受欢迎的答案展示了更好的Swift实践:没有强制解包,优先考虑URL而不是路径,并且使用Data而不是NSData - Luke Rogers

8
如此回答所述:
当单元测试工具运行您的代码时,您的单元测试包不是主要的包。即使您正在运行测试,而不是应用程序,您的应用程序包仍然是主要的包。
如果您使用以下代码,则您的代码将搜索包含单元测试类的包,一切都将正常。 Objective C:
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"TestData" ofType:@"xml"];
NSData *xmlData = [NSData dataWithContentsOfFile:path];

Swift:

let bundle = NSBundle(forClass: self.dynamicType)
if let path = bundle.pathForResource("TestData", ofType: "xml")
{
    let xmlData = NSData(contentsOfFile: path)
}

1
在 Swift 的例子中,我认为你在第二行应该使用 "bundle" 而不是 "testBundle"。 - colbadhombre
这个答案基本上与一年前提供的答案相同。 - mac10688
@stevo.mit 对不起! - mac10688

3

Swift 4+

我发现这里没有最新的回答或使用URL。

首先,将以下函数添加到您的testClass中:

/// getFile. Opens a file in the current bundle and return as data
/// - Parameters:
///   - name: fileName
///   - withExtension: extension name, i.e. "json"
/// - Returns: Data of the contents of the file on nil if not found
static func getFile(_ name: String, withExtension: String) -> Data? {
    guard let url = Bundle(for: Self.self)
            .url(forResource: name, withExtension: withExtension) else { return nil }
    guard let data = try? Data(contentsOf: url) else { return nil }
    return data
}

那么你可以在测试类中这样调用它:

func test() throws {
    let xmlData = Self.getFile("TestData", withExtension: "xml")
    XCTAssertNotNil(xmlData, "File not found")
}

注意:为了安全起见,苹果建议始终使用URL来获取资源(而不是路径)。

2

相对路径是相对于当前工作目录的。默认情况下,它在根目录 / 下查找该文件夹。它在您的启动磁盘的根级别上寻找该文件夹。

获取存储在包内资源的正确方式是向您的包请求其内容。

在应用程序中,您可以使用 [NSBundle mainBundle] 获取包。我不知道它是否适用于测试用例;尝试一下,如果它不行(如果返回 nil 或一个无用的包对象),请替换为 [NSBundle bundleForClass:[self class]]

无论哪种方式,一旦您获得了包,就可以请求它的资源的路径或 URL。您通常应该选择 URL,除非您有非常特定的原因需要路径(例如将其传递给使用 NSTask 的命令行工具)。发送一个 URLForResource:withExtension: 消息以获取资源的 URL。

然后,为从中读取字符串,使用 [NSString stringWithContentsOfURL:encoding:error:],并传入您从包获取的 URL。


1
所以我发现使用标识符加载捆绑包是有效的。尽管这个标识符不是 [NSBundle allBundles] 数组的一部分。理想情况下,它应该列出来... - ArdenDev

1

需要注意的一件事是(这让我浪费了几分钟才明白):

不要忘记将测试文件添加到“Build Phases”中的“Copy Bundle Resources”部分

如果没有这样做,你将无法成功加载该文件。


1
  1. Get main bundle

  2. Use main bundle to get local mock data

  3. Load data from JSON

    let mainBundle = Bundle(identifier: "com.mainBundle.Identifier")
    if let path = mainBundle?.path(forResource: "mockData", ofType: "json") {
         do {
             let testData = try Data(contentsOf: URL(fileURLWithPath: path))
             networkTestSession.data = testData
         } catch {
             debugPrint("local json test data missing")
         }
     }
    

-2

我知道这个具体的问题是关于xml文件的,但如果你想用json文件做同样的事情,可以尝试这个:

let url = Bundle.main.url(forResource: "data", withExtension: ".json") guard let dataURL = url, let data = try? Data(contentsOf: dataURL) else { fatalError("无法读取data.json文件") }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接