Perché il codice all'interno dei test unitari non trova risorse bundle?


184

Alcuni codici I am unit testing devono caricare un file di risorse. Contiene la seguente riga:

NSString *path = [[NSBundle mainBundle] pathForResource:@"foo" ofType:@"txt"];

Nell'app funziona bene, ma quando viene eseguito dal framework di unit testing pathForResource:restituisce zero, il che significa che non è in grado di individuare foo.txt.

Mi sono assicurato che foo.txtfosse incluso nella fase di creazione delle risorse del pacchetto copia della destinazione del test unitario, quindi perché non riesce a trovare il file?

Risposte:


316

Quando il cablaggio dell'unità di prova esegue il codice, il pacchetto unità di prova NON è il pacchetto principale.

Anche se stai eseguendo dei test, non la tua applicazione, il pacchetto di applicazioni è ancora il pacchetto principale. (Presumibilmente, questo impedisce al codice che stai testando di cercare nel bundle sbagliato.) Pertanto, se aggiungi un file di risorse al bundle di unit test, non lo troverai se cerchi il bundle principale. Se si sostituisce la riga sopra con:

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *path = [bundle pathForResource:@"foo" ofType:@"txt"];

Quindi il tuo codice cercherà il pacchetto in cui si trova la tua classe di test unitario e tutto andrà bene.


Non funziona per me. Ancora il bundle di build e non il bundle di test.
Chris

1
@Chris Nella riga di esempio presumo si selfriferisca a una classe nel bundle principale, non alla classe del caso di test. Sostituisci [self class]con qualsiasi classe nel tuo pacchetto principale. Modificherò il mio esempio.
benzado,

@benzado Il bundle è sempre lo stesso (build), penso sia corretto. Perché quando sto usando self o AppDelegate, entrambi si trovano nel bundle principale. Quando controllo le fasi di compilazione del target principale sono presenti entrambi i file. Ma ciò che voglio differire tra bundle principale e test in fase di esecuzione. Il codice in cui ho bisogno del pacchetto è nel pacchetto principale. Ho un problema seguente. Sto caricando un file png. Normalmente questo file non è nel bundle principale perché l'utente lo scarica da un server. Ma per un test voglio usare un file dal bundle di test senza copiarlo nel bundle principale.
Chris,

2
@ Chris Ho fatto un errore con la mia modifica precedente e ho modificato di nuovo la risposta. Al momento del test, il bundle dell'app è ancora il bundle principale. Se si desidera caricare un file di risorse che si trova nel bundle di unit test, è necessario utilizzare bundleForClass:con una classe nel bundle di unit test. Dovresti ottenere il percorso del file nel tuo codice unit test, quindi passare la stringa del percorso all'altro tuo codice.
benzado,

Funziona, ma come posso distinguere tra una distribuzione in esecuzione e una distribuzione in prova? In base al fatto se si tratta di un test, ho bisogno di una risorsa dal bundle di test in una classe nel bundle principale. Se è una 'corsa' regolare, ho bisogno di una risorsa dal pacchetto principale e non dal pacchetto di test. Qualche idea?
Chris,

80

Un'implementazione rapida:

Swift 2

let testBundle = NSBundle(forClass: self.dynamicType)
let fileURL = testBundle.URLForResource("imageName", withExtension: "png")
XCTAssertNotNil(fileURL)

Swift 3, Swift 4

let testBundle = Bundle(for: type(of: self))
let filePath = testBundle.path(forResource: "imageName", ofType: "png")
XCTAssertNotNil(filePath)

Il pacchetto fornisce modi per scoprire i percorsi principali e di test per la tua configurazione:

@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

In Xcode 6 | 7 | 8 | 9, un percorso di bundle di unit test sarà in Developer/Xcode/DerivedDataqualcosa come ...

/Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      foo.txt

... che è separato dal Developer/CoreSimulator/Devices normale percorso del bundle (non unit-test) :

/Users/
  UserName/
    Library/
    Developer/
      CoreSimulator/
        Devices/
          _UUID_/
            data/
              Containers/
                Bundle/
                  Application/
                    _UUID_/
                      App.app/

Si noti inoltre che l'eseguibile di unit test è, per impostazione predefinita, collegato al codice dell'applicazione. Tuttavia, il codice test unitario deve avere solo l'appartenenza target solo nel pacchetto test. Il codice dell'applicazione deve avere solo l'appartenenza target nel pacchetto dell'applicazione. In fase di esecuzione, il bundle target unit test viene iniettato nel bundle dell'applicazione per l'esecuzione .

Swift Package Manager (SPM) 4:

let testBundle = Bundle(for: type(of: self)) 
print("testBundle.bundlePath = \(testBundle.bundlePath) ")

Nota: per impostazione predefinita, la riga di comando swift testcreerà un MyProjectPackageTests.xctestpacchetto di test. E, swift package generate-xcodeprojcreerà un MyProjectTests.xctestpacchetto di test. Questi diversi bundle di test hanno percorsi diversi . Inoltre, i diversi bundle di test potrebbero presentare una struttura di directory interna e differenze di contenuto .

In entrambi i casi, .bundlePathe .bundleURLrestituirà il percorso del bundle di test attualmente in esecuzione su macOS. Tuttavia, Bundlenon è attualmente implementato per Ubuntu Linux.

Inoltre, la riga di comando swift builde al swift testmomento non fornisce un meccanismo per la copia delle risorse.

Tuttavia, con un certo sforzo, è possibile impostare processi per utilizzare Swift Package Manger con risorse negli ambienti macOS Xcode, riga di comando macOS e riga di comando Ubuntu. Un esempio è disponibile qui: 004.4'2 SW Dev Swift Package Manager (SPM) con risorse Qref

Vedi anche: Usa le risorse nei test unitari con Swift Package Manager

Swift Package Manager (SPM) 4.2

Swift Package Manager PackageDescription 4.2 introduce il supporto delle dipendenze locali .

Le dipendenze locali sono pacchetti su disco a cui è possibile fare riferimento direttamente utilizzando i loro percorsi. Le dipendenze locali sono consentite solo nel pacchetto radice e sovrascrivono tutte le dipendenze con lo stesso nome nel grafico del pacchetto.

Nota: mi aspetto, ma non ho ancora testato, che qualcosa di simile al seguente dovrebbe essere possibile con 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"]
        ),
    ]
)

1
Anche per Swift 4, puoi usare Bundle (per: tipo (di: self))
Rocket Garden

14

Con Swift Swift 3 la sintassi self.dynamicTypeè stata deprecata, utilizzare invece questa

let testBundle = Bundle(for: type(of: self))
let fooTxtPath = testBundle.path(forResource: "foo", ofType: "txt")

o

let fooTxtURL = testBundle.url(forResource: "foo", withExtension: "txt")

4

Confermare che la risorsa sia stata aggiunta alla destinazione del test.

inserisci qui la descrizione dell'immagine


2
L'aggiunta di risorse al pacchetto di test rende i risultati del test in gran parte non validi. Dopotutto, una risorsa potrebbe trovarsi facilmente nella destinazione del test ma non nella destinazione dell'app, e tutti i test passerebbero, ma l'app scoppierebbe in fiamme.
dgatwood,

1

se hai più target nel tuo progetto, allora devi aggiungere risorse tra diversi target disponibili nell'abbonamento Target e potresti dover passare da un target diverso come 3 passaggi mostrati nella figura sotto

inserisci qui la descrizione dell'immagine


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.