使用 Swift Package Manager 在单元测试中使用资源 [英] Use resources in unit tests with 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)".:-)
<块引用>资源并不总是供包的客户使用;资源的一种用途可能包括仅单元测试需要的测试装置.此类资源不会与库代码一起合并到包的客户端中,而只会在运行包的测试时使用.
- 在
target
和testTarget
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 intarget
andtestTarget
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
- Swift 5.3 SPM Resources in tests uses wrong bundle path?
- Swift Package Manager - Resources in test targets
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屋!