复制资源文件以供 Xcode SPM 测试使用

4

我对Swift Package Manager不是很熟悉,但因为它被整合进Xcode 11中,所以现在是时候尝试一下了。我有一个新的应用程序和SPM库,在一个新的工作区内。我有一个带有测试的工作库,并已成功将该库导入应用程序。

我需要通过解析json文件来扩展SPM库并添加新的测试。我了解到资源目录功能不受支持。唯一可行的方案似乎是在库构建过程中添加文件复制步骤,以便可执行文件可以发现资源文件。

我可以通过命令行弄清楚如何做到这一点,但无法在Xcode运行构建和测试时完成。对于Swift Packages,没有“Copy Bundle Resources”构建阶段。事实上,一切似乎都被Xcode隐藏了。

我已在SPM中查找类似Makefile类型的文件,以允许我编辑默认的命令行操作,从而绕过Xcode;但我没有看到它们。

是否有某种方式可以交互/控制Xcode 11构建SPM目标,以便我可以将非代码文件复制到测试目标中?


我正想问同样的问题。据我所知,由于它们是静态库,因此您无法将非代码复制到任何SPM目标中。但是,似乎您只需在项目的测试文件夹中创建一个文件夹,然后在运行测试时获取对该文件夹的引用即可。 - jamone
测试可执行文件不是交付库的一部分。我可以使用Bundle(for:)函数获取测试目录位置的绝对路径,然后从那里构建到我的资源目录的路径。但问题在于我的JSON文件必须在构建过程中复制到那里。 - Price Ringo
从我看到的情况来看,测试目录在Derived data中。因此,除了硬编码路径外,没有其他方法可以从那里进入我的源目录引用数据文件,但这对其他开发人员或CI并不起作用。 - jamone
2个回答

4

搞定了!!!

struct Resource {
  let name: String
  let type: String
  let url: URL

  init(name: String, type: String, sourceFile: StaticString = #file) throws {
    self.name = name
    self.type = type

    // The following assumes that your test source files are all in the same directory, and the resources are one directory down and over
    // <Some folder>
    //  - Resources
    //      - <resource files>
    //  - <Some test source folder>
    //      - <test case files>
    let testCaseURL = URL(fileURLWithPath: "\(sourceFile)", isDirectory: false)
    let testsFolderURL = testCaseURL.deletingLastPathComponent()
    let resourcesFolderURL = testsFolderURL.deletingLastPathComponent().appendingPathComponent("Resources", isDirectory: true)
    self.url = resourcesFolderURL.appendingPathComponent("\(name).\(type)", isDirectory: false)
  }
}

使用方法:

final class SPMTestDataTests: XCTestCase {
  func testExample() throws {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct
    // results.
    XCTAssertEqual(SPMTestData().text, "Hello, World!")

    let file = try Resource(name: "image", type: "png")
    let image = UIImage(contentsOfFile: file.url.path)
    print(image)
  }
}

enter image description here

我发现使用#file这里的关键。


4

这是另一种提供测试资源访问权限的解决方案。希望能尽快得到OP问题的答案。

使用以下代码,可以创建一个扩展程序,允许调用者创建用于访问测试资源的URL。

let url = URL(forResource: "payload", type: "json")

这段代码要求所有资源文件都位于测试目标下面名为“Resources”的扁平目录中。

// MARK: - ./Resources/ Workaround
// URL of the directory containing non-code, test resource fi;es.
//
// It is required that a directory named "Resources" be contained immediately below the test target.
// Root
//   Package.swift
//   Tests
//     (target)
//       Resources
//
fileprivate let _resources: URL = {
    func packageRoot(of file: String) -> URL? {
        func isPackageRoot(_ url: URL) -> Bool {
            let filename = url.appendingPathComponent("Package.swift", isDirectory: false)
            return FileManager.default.fileExists(atPath: filename.path)
        }

        var url = URL(fileURLWithPath: file, isDirectory: false)
        repeat {
            url = url.deletingLastPathComponent()
            if url.pathComponents.count <= 1 {
                return nil
            }
        } while !isPackageRoot(url)
        return url
    }

    guard let root = packageRoot(of: #file) else {
        fatalError("\(#file) must be contained in a Swift Package Manager project.")
    }
    let fileComponents = URL(fileURLWithPath: #file, isDirectory: false).pathComponents
    let rootComponenets = root.pathComponents
    let trailingComponents = Array(fileComponents.dropFirst(rootComponenets.count))
    let resourceComponents = rootComponenets + trailingComponents[0...1] + ["Resources"]
    return URL(fileURLWithPath: resourceComponents.joined(separator: "/"), isDirectory: true)
}()

extension URL {
    init(forResource name: String, type: String) {
        let url = _resources.appendingPathComponent("\(name).\(type)", isDirectory: false)
        self = url
    }
}

谢谢您。我稍微重构了一下,并将其封装在一个 SPM 模块中:https://github.com/eonist/ResourceHelper - Sentry.co

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