一个 Swift 实现:
斯威夫特 2
let testBundle = NSBundle(forClass: self.dynamicType)
let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
XCTAssertNotNil(fileURL)
斯威夫特 3、斯威夫特 4
let testBundle = Bundle(for: type(of: self))
let filePath = testBundle.path(forResource: "imageName", ofType: "png")
XCTAssertNotNil(filePath)
Bundle 提供了发现配置的主要路径和测试路径的方法:
@testable import Example
class ExampleTests: XCTestCase {
func testExample() {
let bundleMain = Bundle.main
let bundleDoingTest = Bundle(for: type(of: self ))
let bundleBeingTested = Bundle(identifier: "com.example.Example")!
print("bundleMain.bundlePath : \(bundleMain.bundlePath)")
// …/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents
print("bundleDoingTest.bundlePath : \(bundleDoingTest.bundlePath)")
// …/PATH/TO/Debug/ExampleTests.xctest
print("bundleBeingTested.bundlePath : \(bundleBeingTested.bundlePath)")
// …/PATH/TO/Debug/Example.app
print("bundleMain = " + bundleMain.description) // Xcode Test Agent
print("bundleDoingTest = " + bundleDoingTest.description) // Test Case Bundle
print("bundleUnderTest = " + bundleBeingTested.description) // App Bundle
在 Xcode 6|7|8|9 中,单元测试包路径将位于 Developer/Xcode/DerivedData 类似 ...
/Users/
UserName/
Library/
Developer/
Xcode/
DerivedData/
App-qwertyuiop.../
Build/
Products/
Debug-iphonesimulator/
AppTests.xctest/
foo.txt
...与Developer/CoreSimulator/Devices 分开常规(非单元测试)捆绑路径:
/Users/
UserName/
Library/
Developer/
CoreSimulator/
Devices/
_UUID_/
data/
Containers/
Bundle/
Application/
_UUID_/
App.app/
还请注意,单元测试可执行文件默认情况下与应用程序代码链接。但是,单元测试代码应该只在测试包中具有 Target Membership。应用程序代码应仅在应用程序包中具有目标成员资格。在运行时,单元测试目标包是injected into the application bundle for execution。
Swift 包管理器 (SPM) 4:
let testBundle = Bundle(for: type(of: self))
print("testBundle.bundlePath = \(testBundle.bundlePath) ")
注意:默认情况下,命令行swift test 将创建一个MyProjectPackageTests.xctest 测试包。并且,swift package generate-xcodeproj 将创建一个 MyProjectTests.xctest 测试包。这些不同的测试包具有不同的路径。 另外,不同的测试包可能有一些内部目录结构和内容差异。
在任何一种情况下,.bundlePath 和 .bundleURL 都将返回当前在 macOS 上运行的测试包的路径。但是,Bundle 目前还没有为 Ubuntu Linux 实现。
此外,命令行swift build 和swift test 目前不提供复制资源的机制。
但是,通过一些努力,可以在 macOS Xcode、macOS 命令行和 Ubuntu 命令行环境中设置使用 Swift Package Manger 的进程。一个例子可以在这里找到:004.4'2 SW Dev Swift Package Manager (SPM) With Resources Qref
另见:Use resources in unit tests with Swift Package Manager
Swift 包管理器 (SwiftPM) 5.3
Swift 5.3 包含 Package Manager Resources SE-0271 进化提案,其“状态:已实施 (Swift 5.3)”。 :-)
资源并不总是供软件包的客户使用;资源的一种用途可能包括仅单元测试需要的测试夹具。此类资源不会与库代码一起合并到包的客户端中,而只会在运行包的测试时使用。
- 在
target 和testTarget API 中添加新的resources 参数以允许显式声明资源文件。
SwiftPM 使用文件系统约定来确定属于包中每个目标的源文件集:具体而言,目标的源文件是位于目标的指定“目标目录”下的那些。默认情况下,这是一个与目标同名的目录,位于“Sources”(用于常规目标)或“Tests”(用于测试目标)中,但可以在包清单中自定义此位置。
// 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: "CLIQuickstartLib",
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("Resources"),
]),
.testTarget(
name: "CLIQuickstartLibTests",
dependencies: [],
resources: [
// Copy 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()),因此在 Xcode 构建时不会出现在 Foundation.Bundle 中。
Xcode 中类似的方法是手动将 Resources 引用文件夹添加到模块,添加 Xcode 构建阶段 copy 以将 Resource 放入某个 *.bundle 目录,然后添加 #ifdef Xcode用于 Xcode 构建以使用资源的编译器指令。
#if Xcode
extension Foundation.Bundle {
/// Returns resource bundle as a `Bundle`.
/// Requires Xcode copy phase to locate files into `*.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("Bundle.module could not load: \(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