为什么单元测试中的代码无法找到包资源?


183

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

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

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

我已经确保它foo.txt包含在单元测试目标的“ 复制捆绑资源”构建阶段中,那么为什么找不到文件?

Answers:


316

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

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

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

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


对我不起作用。仍然是构建包,而不是测试包。
克里斯

1
@Chris在示例行中,我self指的是主包中的类,而不是测试用例类。替换[self class]为主捆绑中的任何类。我将编辑我的示例。
benzado

@benzado捆绑包仍然是相同的(内部版本),我认为这是正确的。因为当我使用self或AppDelegate时,两者都位于主捆绑包中。当我检查主要目标的构建阶段时,两个文件都进入了。但是我想在运行时在主要捆绑包和测试捆绑包之间有所不同。我需要捆绑软件的代码在主捆绑软件中。我有以下问题。我正在加载png文件。通常,此文件不在主捆绑包中,因为用户是从服务器下载的。但是对于测试,我想使用测试包中的文件而不将其复制到主包中。
克里斯

2
@Chris我之前的编辑有误,然后再次编辑了答案。在测试时,应用程序包仍然是主要包。如果要加载单元测试包中的资源文件,则需要bundleForClass:与单元测试包中的类一起使用。您应该在单元测试代码中获取文件的路径,然后将路径字符串传递给其他代码。
benzado 2012年

这可行,但是如何区分运行部署和测试部署?基于这样的事实,如果它是测试,则需要来自主捆绑包中的类中的测试捆绑包中的资源。如果是常规的“运行”,则需要主捆绑包中的资源,而不是测试捆绑包中的资源。任何想法?
克里斯

78

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

捆绑包提供了发现配置主要路径和测试路径的方法:

@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 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测试包。这些不同的测试包具有不同的路径另外,不同的测试包可能会有一些内部目录结构和内容差异

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

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

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

也可以看看: 通过Swift Package Manager在单元测试中使用资源

Swift套件管理员(SPM)4.2

Swift Package Manager PackageDescription 4.2引入了对本地依赖项的支持

本地相关性是磁盘上的程序包,可以使用其路径直接引用。本地依赖项仅在根包中允许,它们会在包图中覆盖具有相同名称的所有依赖项。

注意:我希望但尚未经过测试,使用SPM 4.2应该可以进行以下操作:

// swift-tools-version:4.2
import PackageDescription

let package = Package(
    name: "MyPackageTestResources",
    dependencies: [
        .package(path: "../test-resources"),
    ],
    targets: [
        // ...
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage", "MyPackageTestResources"]
        ),
    ]
)

1
对于Swift 4,您也可以使用Bundle(for:type(of:self))
Rocket Garden

14

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

4

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

在此处输入图片说明


2
向测试包中添加资源会使测试结果在很大程度上无效。毕竟,资源很容易位于测试目标中,而不位于应用程序目标中,您的测试将全部通过,但应用程序将大放异彩。
dgatwood '16

1

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

在此处输入图片说明


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.