在单元测试中使用资源

在单元测试中使用资源

本文介绍了使用 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)". :-)

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

08-24 00:59