Use resources in unit tests with Swift Package Manager

前端 未结 7 483
孤街浪徒
孤街浪徒 2020-12-08 21:12

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:

7条回答
  •  感情败类
    2020-12-08 21:30

    A Swift script approach for Swift 5.2 and earlier...

    Swift Package Manager (SwiftPM)

    It is possible to use resources in unit tests with SwiftPM for both macOS and Linux with some additional setup and custom scripts. Here is a description of one possible approach:

    The SwiftPM does not yet provide a mechanism for handling resources. The following is a workable approach for using test resources TestResources/ within a package; and, also provides for a consistent TestScratch/ directory for creating test files if needed.

    Setup:

    • Add test resources directory TestResources/ in the PackageName/ directory.

    • For Xcode use, add test resources to project "Build Phases" for the test bundle target.

      • Project Editor > TARGETS > CxSQLiteFrameworkTests > Build Phases > Copy Files: Destination Resources, + add files
    • For command line use, set up Bash aliases which include swift-copy-testresources.swift

    • Place an executable version of swift-copy-testresources.swift on an appropriate path which is included $PATH.

      • Ubuntu: nano ~/bin/ swift-copy-testresources.swift

    Bash Aliases

    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. Apppend to end. Change /opt/swift/current to where Swift is installed for a given system.

    #############
    ### 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;'
    

    Script: swift-copy-testresources.sh chmod +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..

    Test Utility Code

    ////////////////
    // MARK: - 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
    

    File Locations:

    Linux

    During the swift build and swift test the process environment variable PWD provides a path the package root …/PackageName. The PackageName/TestResources/ files are copied to $PWD/.buid/TestResources. The TestScratch/ directory, if used during test runtime, is created in $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/ files are copied into the test bundle Contents/Resources folder as part of the Build Phases. If used during tests, TestScratch/ is placed alongside the *xctest bundle.

    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)
    

    I also posted a GitHubGist of this same approach at 004.4'2 SW Dev Swift Package Manager (SPM) With Resources Qref

提交回复
热议问题