为什么单元测试中的代码不能找到捆绑资源?

ben*_*ado 173 xcode cocoa unit-testing nsbundle octest

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

NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];
Run Code Online (Sandbox Code Playgroud)

在应用程序中它运行得很好,但是当单元测试框架运行时pathForResource:返回nil,意味着它找不到foo.txt.

我已经确定它foo.txt包含在单元测试目标的Copy Bundle Resources构建阶段中,为什么它找不到该文件呢?

ben*_*ado 302

当单元测试工具运行代码时,单元测试包不是主要包.

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

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"foo" ofType:@"txt"];
Run Code Online (Sandbox Code Playgroud)

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

  • @Chris我在之前的编辑中犯了一个错误,并再次编辑了答案.在测试时,应用程序包仍然是主要捆绑包.如果要加载单元测试包中的资源文件,则需要在单元测试包中使用`bundleForClass:`和类.您应该在单元测试代码中获取文件的路径,然后将路径字符串传递给您的其他代码. (2认同)

l -*_*c l 70

Swift实现:

斯威夫特2

let testBundle = NSBundle(forClass: self.dynamicType)
let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
XCTAssertNotNil(fileURL)
Run Code Online (Sandbox Code Playgroud)

Swift 3,Swift 4

let testBundle = Bundle(for: type(of: self))
let filePath = testBundle.path(forResource: "imageName", ofType: "png")
XCTAssertNotNil(filePath)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在Xcode 6 | 7 | 8 | 9中,单元测试包路径Developer/Xcode/DerivedData类似于......

/Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      foo.txt
Run Code Online (Sandbox Code Playgroud)

...与Developer/CoreSimulator/Devices 常规(非单元测试)捆绑路径分开:

/Users/
  UserName/
    Library/
    Developer/
      CoreSimulator/
        Devices/
          _UUID_/
            data/
              Containers/
                Bundle/
                  Application/
                    _UUID_/
                      App.app/
Run Code Online (Sandbox Code Playgroud)

另请注意,默认情况下,单元测试可执行文件与应用程序代码链接.但是,单元测试代码应仅在测试包中具有目标成员资格.应用程序代码应仅在应用程序包中具有目标成员身份.在运行时,将单元测试目标包注入应用程序包中以供执行.

Swift Package Manager(SPM)4:

let testBundle = Bundle(for: type(of: self)) 
print("testBundle.bundlePath = \(testBundle.bundlePath) ")
Run Code Online (Sandbox Code Playgroud)

注意:默认情况下,命令行将swift test创建MyProjectPackageTests.xctest测试包.并且,swift package generate-xcodeproj将创建一个MyProjectTests.xctest测试包.这些不同的测试包具有不同的路径.此外,不同的测试包可能具有一些内部目录结构和内容差异.

在任何一种情况下,.bundlePath.bundleURL将返回当前在macOS上运行的测试包的路径.但是,Bundle目前还没有为Ubuntu Linux实现.

此外,命令行swift buildswift test目前不提供复制资源的机制.

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

另请参阅:使用Swift Package Manager在单元测试中使用资源

Swift Package Manager(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"]
        ),
    ]
)
Run Code Online (Sandbox Code Playgroud)


Mar*_*Him 13

使用swift Swift 3语法self.dynamicType已被弃用,请改用它

let testBundle = Bundle(for: type(of: self))
let fooTxtPath = testBundle.path(forResource: "foo", ofType: "txt")
Run Code Online (Sandbox Code Playgroud)

要么

let fooTxtURL = testBundle.url(forResource: "foo", withExtension: "txt")
Run Code Online (Sandbox Code Playgroud)


mis*_*may 5

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

在此处输入图片说明

  • 将资源添加到测试包会使测试结果在很大程度上无效。毕竟,一个资源很容易在测试目标中,但不在应用目标中,你的测试都会通过,但应用会突然起火。 (3认同)