swift - 使用 Swift 包管理器在单元测试中使用资源

标签 swift swift-package-manager

我试图在单元测试中使用资源文件并使用 Bundle.path 访问它,但它返回 nil。

MyProjectTests.swift 中的这个调用返回 nil:

Bundle(for: type(of: self)).path(forResource: "TestAudio", ofType: "m4a")

这是我的项目层次结构。我还尝试将 TestAudio.m4a 移动到 Resources 文件夹:

├── Package.swift
├── Sources
│   └── MyProject
│       ├── ...
└── Tests
    └── MyProjectTests
        ├── MyProjectTests.swift
        └── TestAudio.m4a

这是我的包裹描述:

// swift-tools-version:4.0

import PackageDescription

let package = Package(
    name: "MyProject",
    products: [
        .library(
            name: "MyProject",
            targets: ["MyProject"])
    ],
    targets: [
        .target(
            name: "MyProject",
            dependencies: []
        ),
        .testTarget(
            name: "MyProjectTests",
            dependencies: ["MyProject"]
        ),
    ]
)

我使用的是 Swift 4 和 Swift Package Manager Description API version 4。

最佳答案

swift 5.3

请参阅 Apple 文档:"Bundling Resources with a Swift Package"

Swift 5.3 包括 Package Manager Resources SE-0271带有“状态:已实现(Swift 5.3)”的进化提案。

Resources aren't always intended for use by clients of the package; one use of resources might include test fixtures that are only needed by unit tests. Such resources would not be incorporated into clients of the package along with the library code, but would only be used while running the package's tests.

  • Add a new resources parameter in target and testTarget APIs to allow declaring resource files explicitly.

SwiftPM uses file system conventions for determining the set of source files that belongs to each target in a package: specifically, a target's source files are those that are located underneath the designated "target directory" for the target. By default this is a directory that has the same name as the target and is located in "Sources" (for a regular target) or "Tests" (for a test target), but this location can be customized in the package manifest.

// Get path to DefaultSettings.plist file.
let path = Bundle.module.path(forResource: "DefaultSettings", ofType: "plist")

// Load an image that can be in an asset archive in a bundle.
let image = UIImage(named: "MyIcon", in: Bundle.module, compatibleWith: UITraitCollection(userInterfaceStyle: .dark))

// Find a vertex function in a compiled Metal shader library.
let shader = try mtlDevice.makeDefaultLibrary(bundle: Bundle.module).makeFunction(name: "vertexShader")

// Load a texture.
let texture = MTKTextureLoader(device: mtlDevice).newTexture(name: "Grass", scaleFactor: 1.0, bundle: Bundle.module, options: options)

示例

// swift-tools-version:5.3
import PackageDescription

  targets: [
    .target(
      name: "Example",
      dependencies: [],
      resources: [
        // Apply platform-specific rules.
        // For example, images might be optimized per specific platform rule.
        // If path is a directory, the rule is applied recursively.
        // By default, a file will be copied if no rule applies.
        // Process file in Sources/Example/Resources/*
        .process("Resources"),
      ]),
    .testTarget(
      name: "ExampleTests",
      dependencies: [Example],
      resources: [
        // Copy Tests/ExampleTests/Resources directories as-is. 
        // Use to retain directory structure.
        // Will be at top level in bundle.
        .copy("Resources"),
      ]),

报告的问题和可能的解决方法

Xcode

Bundle.module 由 SwiftPM 生成(参见 Build/BuildPlan.swift SwiftTargetBuildDescription generateResourceAccessor() )因此不存在于 Foundation.Bundle 中当由 Xcode 构建时。

Xcode 中的类似方法是:

  1. 手动添加一个Resources引用文件夹到Xcode项目,
  2. 添加 Xcode 构建阶段 copyResource 放入某个 *.bundle 目录,
  3. 为 Xcode 构建添加一些自定义 #ifdef XCODE_BUILD 编译器指令以使用资源。
#if XCODE_BUILD
extension Foundation.Bundle {
    
    /// Returns resource bundle as a `Bundle`.
    /// Requires Xcode copy phase to locate files into `ExecutableName.bundle`;
    /// or `ExecutableNameTests.bundle` for test resources
    static var module: Bundle = {
        var thisModuleName = "CLIQuickstartLib"
        var url = Bundle.main.bundleURL
        
        for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
            url = bundle.bundleURL.deletingLastPathComponent()
            thisModuleName = thisModuleName.appending("Tests")
        }
        
        url = url.appendingPathComponent("\(thisModuleName).bundle")
        
        guard let bundle = Bundle(url: url) else {
            fatalError("Foundation.Bundle.module could not load resource bundle: \(url.path)")
        }
        
        return bundle
    }()
    
    /// Directory containing resource bundle
    static var moduleDir: URL = {
        var url = Bundle.main.bundleURL
        for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
            // remove 'ExecutableNameTests.xctest' path component
            url = bundle.bundleURL.deletingLastPathComponent()
        }
        return url
    }()
    
}
#endif

关于swift - 使用 Swift 包管理器在单元测试中使用资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47177036/

相关文章:

swift - 将文件拖动到 Cocoa 的 WKWebView 上的可拖动 View 时,光标未正确更改

ios - 无法在谷歌地图 ios sdk 上获取当前位置(GPS)

swift - 只有继承自 NSObject 的类才能声明为 @objc

ios - 从 cocoapods 过渡到 Swift Package Manager

swift - 如何使用 Swift Pacakge Manager 从 C 语言目标内部链接到系统模块?

ios - 了解隐藏在状态栏中的神奇数字

swift - 在 Web 服务器 iOS 应用程序上安装 TLS 证书

ios - Swift 包 : Dependent Package can't be found. 补救措施?

Swift 包 : error: product dependency 'LineNoise' in package 'linenoise-swift' not found

Swift 包和相互冲突的依赖项