使用 Swift Package Manager 在单元测试中使用资源 [英] Use resources in unit tests with Swift Package Manager

查看:37
本文介绍了使用 Swift Package Manager 在单元测试中使用资源的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

├── Package.swift├── 资料来源│ └── 我的项目│ ├── ...└── 测试└── MyProjectTests├── MyProjectTests.swift└── TestAudio.m4a

这是我的包裹描述:

//swift-tools-version:4.0导入包描述让包=包(name: "我的项目",产品: [.图书馆(name: "我的项目",目标:[我的项目"])],目标:[.目标(name: "我的项目",依赖项:[]),.testTarget(name: "MyProjectTests",依赖项:[我的项目"]),])

我使用的是 Swift 4 和 Swift Package Manager Description API 版本 4.

解决方案

Swift 5.3

请参阅 Apple 文档:使用 Swift 包捆绑资源"

Swift 5.3 包括 包管理器资源 SE-0271 进化提案,带有状态:已实施(Swift 5.3)".:-)

<块引用>

资源并不总是供包的客户使用;资源的一种用途可能包括仅单元测试需要的测试装置.此类资源不会与库代码一起合并到包的客户端中,而只会在运行包的测试时使用.

  • targettestTarget API 中添加新的 resources 参数以允许显式声明资源文件.

SwiftPM 使用文件系统约定来确定属于包中每个目标的源文件集:具体而言,目标的源文件是位于指定目标目录"下的那些文件.为目标.默认情况下,这是一个与目标同名的目录,位于Sources"目录中.(对于常规目标)或测试"(对于测试目标),但可以在包清单中自定义此位置.

<块引用>

//获取 DefaultSettings.plist 文件的路径.let path = Bundle.module.path(forResource: "DefaultSettings", ofType: "plist")//加载一个图像,该图像可以在一个包中的资产档案中.let image = UIImage(named: "MyIcon", in: Bundle.module, compatibleWith: UITraitCollection(userInterfaceStyle: .dark))//在已编译的 Metal 着色器库中查找顶点函数.让着色器 = 尝试 mtlDevice.makeDefaultLibrary(bundle: Bundle.module).makeFunction(name: "vertexShader")//加载纹理.let texture = MTKTextureLoader(device: mtlDevice).newTexture(name: "Grass", scaleFactor: 1.0, bundle: Bundle.module, options: options)

示例

//swift-tools-version:5.3导入包描述目标:[.目标(名称:示例",依赖项:[],资源: [//应用特定于平台的规则.//例如,图像可能会根据特定的平台规则进行优化.//如果路径是目录,则递归应用规则.//默认情况下,如果没有规则适用,将复制文件.//处理 Sources/Example/Resources 中的文件/*.process("资源"),]),.testTarget(名称:示例测试",依赖项:[示例],资源: [//按原样复制 Tests/ExampleTests/Resources 目录.//用于保留目录结构.//将在包的顶层..copy(资源"),]),

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

Xcode

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

Xcode 中的一个类似方法是手动添加一个 Resources 引用文件夹到 Xcode 项目,添加一个 Xcode 构建阶段 copy 来放置 Resource 进入一些 *.bundle 目录,并为 Xcode 构建添加一些自定义的 #ifdef XCODE_BUILD 编译器指令以使用资源.

#if XCODE_BUILD扩展 Foundation.Bundle {///将资源包作为 `Bundle` 返回.///需要 Xcode 复制阶段才能将文件定位到 `ExecutableName.bundle`;///或 `ExecutableNameTests.bundle` 用于测试资源静态 var 模块:Bundle = {var thisModuleName = "CLIQuickstartLib";var url = Bundle.main.bundleURL对于 Bundle.allBundles 中的 bundle where bundle.bundlePath.hasSuffix(".xctest") {url = bundle.bundleURL.deletingLastPathComponent()thisModuleName = thisModuleName.appending(测试")}url = url.appendingPathComponent("\(thisModuleName).bundle")守卫让捆绑=捆绑(网址:网址)其他{致命错误(Foundation.Bundle.module 无法加载资源包:\(url.path)")}退货包}()///包含资源包的目录静态 var moduleDir: URL = {var url = Bundle.main.bundleURL对于 Bundle.allBundles 中的 bundle where bundle.bundlePath.hasSuffix(".xctest") {//移除 'ExecutableNameTests.xctest' 路径组件url = bundle.bundleURL.deletingLastPathComponent()}返回网址}()}#万一

I'm trying to use a resource file in unit tests and access it with Bundle.path, but it returns nil.

This call in MyProjectTests.swift returns nil:

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

Here is my project hierarchy. I also tried moving TestAudio.m4a to a Resources folder:

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

Here is my package description:

// 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"]
        ),
    ]
)

I am using Swift 4 and the Swift Package Manager Description API version 4.

解决方案

Swift 5.3

See Apple Documentation: "Bundling Resources with a Swift Package"

Swift 5.3 includes Package Manager Resources SE-0271 evolution proposal with "Status: Implemented (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)

Example

// 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"),
      ]),

Reported Issues & Possible Workarounds

Xcode

Bundle.module is generated by SwiftPM (see Build/BuildPlan.swift SwiftTargetBuildDescription generateResourceAccessor()) and thus not present in Foundation.Bundle when built by Xcode.

A comparable approach in Xcode would be to manually add a Resources reference folder to the Xcode project, add an Xcode build phase copy to put the Resource into some *.bundle directory, and add a some custom #ifdef XCODE_BUILD compiler directive for the Xcode build to work with the resources.

#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 Package Manager 在单元测试中使用资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆