単体テストでリソースファイルを使用し、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 DescriptionAPIバージョン4を使用しています。
現在、Swiftパッケージマネージャー(SPM)はリソースを処理しません。これは、SPMのバグ追跡システムで発生している問題です https://bugs.Swift.org/browse/SR -2866 。
リソースのサポートがSPMに実装されるまで、結果の実行可能ファイルが実行時にリソースを期待する場所にリソースをコピーします。 Bundle.resourcePath
プロパティを出力することで、これらの場所を知ることができます。たとえばMakefileを使用して、このコピーを自動的に実行します。このようにして、MakefileはSPMの上に「ビルドオーケストレーター」になります。
このアプローチがMacOSとLinuxでどのように機能するかを示す例を作成しました- https://github.com/vadimeisenbergibm/SwiftResourceHandlingExample 。
ユーザーは、make build
およびmake test
の代わりにSwift build
およびSwift test
のmakeコマンドを実行します。 Makeは、リソースを予想される場所にコピーします(MacOSとLinuxでは、実行中とテスト中に異なります)。
SwiftPM(5.1)は、リソースをネイティブにサポートしていません まだ ただし...
単体テストの実行中は、リポジトリが使用可能であることが期待できるため、#file
から派生したものをリソースにロードするだけです。これは、SwiftPMの現存するすべてのバージョンで機能します。
let thisSourceFile = URL(fileURLWithPath: #file)
let thisDirectory = thisSourceFile.deletingLastPathComponent()
let resourceURL = thisDirectory.appendingPathComponent("TestAudio.m4a")
テスト以外の場合、実行時にリポジトリが存在しない場合でも、バイナリサイズは犠牲になりますが、リソースを含めることができます。任意のファイルをSwiftソースに文字列リテラルでbase 64データとして表現することで埋め込むことができます。 Workspace は、そのプロセスを自動化できるオープンソースツールです。 $ workspace refresh resources
。 (免責事項:私はその作者です。)
このファイル を見て別の解決策を見つけました。
次に、パスを使用してバンドルを作成することができます。
let currentBundle = Bundle.allBundles.filter() { $0.bundlePath.hasSuffix(".xctest") }.first!
let realBundle = Bundle(path: "\(currentBundle.bundlePath)/../../../../Tests/MyProjectTests/Resources")
少し醜いですが、Makefileを避けたい場合は機能します。
Swift Package Manager PackageDescription4.2ローカル依存関係 のサポートを導入します。
ローカル依存関係は、パスを使用して直接参照できるディスク上のパッケージです。ローカル依存関係はルートパッケージでのみ許可され、パッケージグラフ内の同じ名前のすべての依存関係を上書きします。
注:SPM 4.2では、次のようなことが可能になると思いますが、まだテストしていません:
// Swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "MyPackageTestResources",
dependencies: [
.package(path: "../test-resources"),
],
targets: [
// ...
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage", "MyPackageTestResources"]
),
]
)
Swift Package Manager for macOSとLinuxの両方で、いくつかの追加のセットアップとカスタムスクリプトを使用して、単体テストでリソースを使用することができます。1つの可能なアプローチの説明を次に示します。
Swift Package Managerは、リソースを処理するためのメカニズムをまだ提供していません。以下は、パッケージ内でテストリソースTestResources/
を使用するための実行可能なアプローチです。 、必要に応じてテストファイルを作成するための一貫したTestScratch/
ディレクトリも提供します。
TestResources/
ディレクトリにテストリソースディレクトリPackageName/
を追加します。Xcodeを使用するには、テストバンドルターゲットのプロジェクト「ビルドフェーズ」にテストリソースを追加します。
+
ファイルの追加コマンドラインで使用するには、Swift-copy-testresources.Swiftを含むBashエイリアスを設定します
nano ~/bin/ Swift-copy-testresources.Swift
Bashエイリアス
macOS:nano .bash_profile
alias swiftbuild='Swift-copy-testresources.Swift $PWD; Swift build -Xswiftc "-target" -Xswiftc "x86_64-Apple-macosx10.13";'
alias swifttest='Swift-copy-testresources.Swift $PWD; Swift test -Xswiftc "-target" -Xswiftc "x86_64-Apple-macosx10.13";'
alias swiftxcode='Swift package generate-xcodeproj --xcconfig-overrides Package.xcconfig; echo "REMINDER: set Xcode build system."'
Ubuntu:nano ~/.profile
。最後に追加します。/opt/Swift/currentをSwiftが特定のシステムにインストールされている場所)に変更します。
#############
### Swift ###
#############
if [ -d "/opt/Swift/current/usr/bin" ] ; then
PATH="/opt/Swift/current/usr/bin:$PATH"
fi
alias swiftbuild='Swift-copy-testresources.Swift $PWD; Swift build;'
alias swifttest='Swift-copy-testresources.Swift $PWD; Swift test;'
スクリプト:Swift-copy-testresources.shchmod +x
#!/usr/bin/Swift
// FILE: Swift-copy-testresources.sh
// verify Swift path with "which -a Swift"
// macOS: /usr/bin/Swift
// Ubuntu: /opt/Swift/current/usr/bin/Swift
import Foundation
func copyTestResources() {
let argv = ProcessInfo.processInfo.arguments
// for i in 0..<argv.count {
// print("argv[\(i)] = \(argv[i])")
// }
let pwd = argv[argv.count-1]
print("Executing Swift-copy-testresources")
print(" PWD=\(pwd)")
let fm = FileManager.default
let pwdUrl = URL(fileURLWithPath: pwd, isDirectory: true)
let srcUrl = pwdUrl
.appendingPathComponent("TestResources", isDirectory: true)
let buildUrl = pwdUrl
.appendingPathComponent(".build", isDirectory: true)
let dstUrl = buildUrl
.appendingPathComponent("Contents", isDirectory: true)
.appendingPathComponent("Resources", isDirectory: true)
do {
let contents = try fm.contentsOfDirectory(at: srcUrl, includingPropertiesForKeys: [])
do { try fm.removeItem(at: dstUrl) } catch { }
try fm.createDirectory(at: dstUrl, withIntermediateDirectories: true)
for fromUrl in contents {
try fm.copyItem(
at: fromUrl,
to: dstUrl.appendingPathComponent(fromUrl.lastPathComponent)
)
}
} catch {
print(" SKIP TestResources not copied. ")
return
}
print(" SUCCESS TestResources copy completed.\n FROM \(srcUrl)\n TO \(dstUrl)")
}
copyTestResources()
テストユーティリティコード
//////////////// //マーク:-Linux //////////////// #if os(Linux)
// /PATH_TO_PACKAGE/PackageName/.build/TestResources
func getTestResourcesUrl() -> URL? {
guard let packagePath = ProcessInfo.processInfo.environment["PWD"]
else { return nil }
let packageUrl = URL(fileURLWithPath: packagePath)
let testResourcesUrl = packageUrl
.appendingPathComponent(".build", isDirectory: true)
.appendingPathComponent("TestResources", isDirectory: true)
return testResourcesUrl
}
// /PATH_TO_PACKAGE/PackageName/.build/TestScratch
func getTestScratchUrl() -> URL? {
guard let packagePath = ProcessInfo.processInfo.environment["PWD"]
else { return nil }
let packageUrl = URL(fileURLWithPath: packagePath)
let testScratchUrl = packageUrl
.appendingPathComponent(".build")
.appendingPathComponent("TestScratch")
return testScratchUrl
}
// /PATH_TO_PACKAGE/PackageName/.build/TestScratch
func resetTestScratch() throws {
if let testScratchUrl = getTestScratchUrl() {
let fm = FileManager.default
do {_ = try fm.removeItem(at: testScratchUrl)} catch {}
_ = try fm.createDirectory(at: testScratchUrl, withIntermediateDirectories: true)
}
}
///////////////////
// MARK: - macOS
///////////////////
#elseif os(macOS)
func isXcodeTestEnvironment() -> Bool {
let arg0 = ProcessInfo.processInfo.arguments[0]
// Use arg0.hasSuffix("/usr/bin/xctest") for command line environment
return arg0.hasSuffix("/Xcode/Agents/xctest")
}
// /PATH_TO/PackageName/TestResources
func getTestResourcesUrl() -> URL? {
let testBundle = Bundle(for: CxSQLiteFrameworkTests.self)
let testBundleUrl = testBundle.bundleURL
if isXcodeTestEnvironment() { // test via Xcode
let testResourcesUrl = testBundleUrl
.appendingPathComponent("Contents", isDirectory: true)
.appendingPathComponent("Resources", isDirectory: true)
return testResourcesUrl
}
else { // test via command line
guard let packagePath = ProcessInfo.processInfo.environment["PWD"]
else { return nil }
let packageUrl = URL(fileURLWithPath: packagePath)
let testResourcesUrl = packageUrl
.appendingPathComponent(".build", isDirectory: true)
.appendingPathComponent("TestResources", isDirectory: true)
return testResourcesUrl
}
}
func getTestScratchUrl() -> URL? {
let testBundle = Bundle(for: CxSQLiteFrameworkTests.self)
let testBundleUrl = testBundle.bundleURL
if isXcodeTestEnvironment() {
return testBundleUrl
.deletingLastPathComponent()
.appendingPathComponent("TestScratch")
}
else {
return testBundleUrl
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.appendingPathComponent("TestScratch")
}
}
func resetTestScratch() throws {
if let testScratchUrl = getTestScratchUrl() {
let fm = FileManager.default
do {_ = try fm.removeItem(at: testScratchUrl)} catch {}
_ = try fm.createDirectory(at: testScratchUrl, withIntermediateDirectories: true)
}
}
#endif
Linux
Swift build
およびSwift test
の間、プロセス環境変数PWD
は、パッケージルート…/PackageName
のパスを提供します。 PackageName/TestResources/
ファイルは$PWD/.buid/TestResources
にコピーされます。 TestScratch/
ディレクトリは、テストの実行時に使用される場合、$PWD/.buid/TestScratch
に作成されます。
.build/
├── debug -> x86_64-unknown-linux/debug
...
├── TestResources
│ └── SomeTestResource.sql <-- (copied from TestResources/)
├── TestScratch
│ └── SomeTestProduct.sqlitedb <-- (created by running tests)
└── x86_64-unknown-linux
└── debug
├── PackageName.build/
│ └── ...
├── PackageNamePackageTests.build
│ └── ...
├── PackageNamePackageTests.swiftdoc
├── PackageNamePackageTests.swiftmodule
├── PackageNamePackageTests.xctest <-- executable, not Bundle
├── PackageName.swiftdoc
├── PackageName.swiftmodule
├── PackageNameTests.build
│ └── ...
├── PackageNameTests.swiftdoc
├── PackageNameTests.swiftmodule
└── ModuleCache ...
macOS CLI
.build/
|-- TestResources/
| `-- SomeTestResource.sql <-- (copied from TestResources/)
|-- TestScratch/
| `-- SomeTestProduct.sqlitedb <-- (created by running tests)
...
|-- debug -> x86_64-Apple-macosx10.10/debug
`-- x86_64-Apple-macosx10.10
`-- debug
|-- PackageName.build/
|-- PackageName.swiftdoc
|-- PackageName.swiftmodule
|-- PackageNamePackageTests.xctest
| `-- Contents
| `-- MacOS
| |-- PackageNamePackageTests
| `-- PackageNamePackageTests.dSYM
...
`-- libPackageName.a
macOS Xcode
PackageName/TestResources/
ファイルは、ビルドフェーズの一部としてテストバンドルContents/Resources
フォルダーにコピーされます。テスト中に使用する場合、TestScratch/
は*xctest
バンドルの横に配置されます。
Build/Products/Debug/
|-- PackageNameTests.xctest/
| `-- Contents/
| |-- Frameworks/
| | |-- ...
| | `-- libswift*.dylib
| |-- Info.plist
| |-- MacOS/
| | `-- PackageNameTests
| `-- Resources/ <-- (aka TestResources/)
| |-- SomeTestResource.sql <-- (copied from TestResources/)
| `-- libswiftRemoteMirror.dylib
`-- TestScratch/
`-- SomeTestProduct.sqlitedb <-- (created by running tests)
また、これと同じアプローチのGitHubGistを 4.4'2 SW Dev Swift Package Manager(SPM)With Resources Qref に投稿しました。
Swift 5.3には Package Manager Resources SE-0271 "Status:Implemented(Swift 5.3)"の進化提案が含まれています。 :-)
リソースは、パッケージのクライアントによる使用を常に意図しているわけではありません。リソースの1つの使用には、単体テストでのみ必要なテストフィクスチャが含まれる場合があります。このようなリソースは、ライブラリコードとともにパッケージのクライアントに組み込まれることはなく、パッケージのテストの実行中にのみ使用されます。
resources
およびtarget
APIに新しいtestTarget
パラメータを追加して、リソースファイルを明示的に宣言できるようにします。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)