Warum kann Code in Unit-Tests keine Bundle-Ressourcen finden?

182

Einige Codes, die ich Unit-Tests mache, müssen eine Ressourcendatei laden. Es enthält die folgende Zeile:

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

In der App läuft es einwandfrei, aber wenn es vom Unit-Test-Framework ausgeführt wird, wird pathForResource:null zurückgegeben, was bedeutet, dass es nicht gefunden werden konnte foo.txt.

Ich habe sichergestellt, dass dies foo.txtin der Erstellungsphase " Copy Bundle Resources" des Unit-Test-Ziels enthalten ist. Warum kann es die Datei nicht finden?

Benzado
quelle

Antworten:

315

Wenn auf dem Unit-Test-Kabelbaum Ihr Code ausgeführt wird, lautet Ihr Unit-Test-Bundle NICHT das Haupt-Bundle.

Auch wenn Sie Tests ausführen, nicht Ihre Anwendung, ist Ihr Anwendungspaket immer noch das Hauptpaket. (Vermutlich verhindert dies, dass der zu testende Code das falsche Bundle durchsucht.) Wenn Sie dem Unit-Test-Bundle eine Ressourcendatei hinzufügen, werden Sie diese nicht finden, wenn Sie das Haupt-Bundle durchsuchen. Wenn Sie die obige Zeile ersetzen durch:

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

Dann durchsucht Ihr Code das Bundle, in dem sich Ihre Unit-Test-Klasse befindet, und alles ist in Ordnung.

Benzado
quelle
Funktioniert bei mir nicht Immer noch das Build-Bundle und nicht das Test-Bundle.
Chris
1
@Chris In der Beispielzeile, von der ich annehme, selfbezieht sie sich auf eine Klasse im Hauptpaket , nicht auf die Testfallklasse. Ersetzen Sie [self class]durch eine beliebige Klasse in Ihrem Hauptpaket. Ich werde mein Beispiel bearbeiten.
Benzado
@benzado Das Bundle ist immer noch das gleiche (Build), was meiner Meinung nach richtig ist. Denn wenn ich self oder AppDelegate verwende, befinden sich beide im Hauptpaket. Wenn ich die Erstellungsphasen des Hauptziels überprüfe, befinden sich beide Dateien in. Aber was ich zur Laufzeit zwischen Haupt- und Testpaket unterscheiden möchte. Der Code, in dem ich das Bundle benötige, befindet sich im Hauptpaket. Ich habe folgendes ein Problem. Ich lade eine PNG-Datei. Normalerweise befindet sich diese Datei nicht im Hauptpaket, da der Benutzer sie von einem Server herunterlädt. Für einen Test möchte ich jedoch eine Datei aus dem Testpaket verwenden, ohne sie in das Hauptpaket zu kopieren.
Chris
2
@ Chris Ich habe einen Fehler mit meiner vorherigen Bearbeitung gemacht und die Antwort erneut bearbeitet. Zum Testzeitpunkt ist das App-Bundle immer noch das Haupt-Bundle. Wenn Sie eine Ressourcendatei laden möchten, die sich im Unit-Test-Bundle befindet, müssen Sie sie bundleForClass:mit einer Klasse im Unit-Test-Bundle verwenden. Sie sollten den Pfad der Datei in Ihrem Unit-Test-Code abrufen und dann die Pfadzeichenfolge an Ihren anderen Code weitergeben.
Benzado
Dies funktioniert, aber wie kann ich zwischen einem Run-Deployment und einem Test-Deployment unterscheiden? Aufgrund der Tatsache, dass es sich um einen Test handelt, benötige ich eine Ressource aus dem Testpaket in einer Klasse im Hauptpaket. Wenn es sich um einen regulären Lauf handelt, benötige ich eine Ressource aus dem Hauptpaket und nicht aus dem Testpaket. Irgendeine Idee?
Chris
77

Eine schnelle Implementierung:

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)

Das Bundle bietet Möglichkeiten, die Haupt- und Testpfade für Ihre Konfiguration zu ermitteln:

@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 befindet sich ein Unit-Test-Bundle-Pfad in Developer/Xcode/DerivedDataetwa ...

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

... die vom Developer/CoreSimulator/Devices regulären Bundle-Pfad (ohne Unit-Test) getrennt ist :

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

Beachten Sie auch, dass die ausführbare Unit-Test-Datei standardmäßig mit dem Anwendungscode verknüpft ist. Der Komponententestcode sollte jedoch nur im Testpaket eine Zielmitgliedschaft enthalten. Der Anwendungscode sollte nur eine Zielmitgliedschaft im Anwendungspaket enthalten. Zur Laufzeit wird das Unit-Test- Zielpaket zur Ausführung in das Anwendungspaket eingefügt .

Swift Package Manager (SPM) 4:

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

Hinweis: Standardmäßig erstellt die Befehlszeile swift testein MyProjectPackageTests.xctestTestpaket. Und das swift package generate-xcodeprojwird ein MyProjectTests.xctestTestpaket erstellen . Diese verschiedenen Testpakete haben unterschiedliche Pfade . Außerdem können die verschiedenen Testpakete einige interne Verzeichnisstrukturen und Inhaltsunterschiede aufweisen .

In beiden Fällen gibt das .bundlePathund .bundleURLden Pfad des Testpakets zurück, das derzeit unter macOS ausgeführt wird. Jedoch,Bundle derzeit jedoch nicht für Ubuntu Linux implementiert.

Auch Kommandozeile swift buildundswift test bieten derzeit keinen Mechanismus zum Kopieren von Ressourcen.

Mit einigem Aufwand ist es jedoch möglich, Prozesse für die Verwendung des Swift Package Managers mit Ressourcen in den Befehlszeilenumgebungen macOS Xcode, macOS command line und Ubuntu einzurichten. Ein Beispiel finden Sie hier: 004.4'2 SW Dev Swift Package Manager (SPM) mit Ressourcen Qref

Siehe auch: Verwenden Sie Ressourcen in Komponententests mit Swift Package Manager

Swift Package Manager (SPM) 4.2

Swift Package Manager PackageDescription 4.2 bietet Unterstützung für lokale Abhängigkeiten .

Lokale Abhängigkeiten sind Pakete auf der Festplatte, auf die über ihre Pfade direkt verwiesen werden kann. Lokale Abhängigkeiten sind nur im Stammpaket zulässig und überschreiben alle gleichnamigen Abhängigkeiten im Paketdiagramm.

Hinweis: Ich erwarte, habe aber noch nicht getestet, dass mit SPM 4.2 Folgendes möglich sein sollte:

// swift-tools-version:4.2
import PackageDescription

let package = Package(
    name: "MyPackageTestResources",
    dependencies: [
        .package(path: "../test-resources"),
    ],
    targets: [
        // ...
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage", "MyPackageTestResources"]
        ),
    ]
)
l - marc l
quelle
1
Auch für Swift 4 können Sie Bundle (für: type (of: self))
Rocket Garden
14

Bei Swift 3 ist die Syntax self.dynamicTypeveraltet. Verwenden Sie stattdessen diese

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

oder

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

Bestätigen Sie, dass die Ressource zum Testziel hinzugefügt wurde.

Geben Sie hier die Bildbeschreibung ein

Mischimay
quelle
2
Durch Hinzufügen von Ressourcen zum Testpaket werden die Testergebnisse weitgehend ungültig. Schließlich könnte sich eine Ressource leicht im Testziel befinden, aber nicht im App-Ziel, und Ihre Tests würden alle bestehen, aber die App würde in Flammen aufgehen.
Dgatwood
1

Wenn Ihr Projekt mehrere Ziele enthält, müssen Sie Ressourcen zwischen verschiedenen Zielen hinzufügen, die in der Zielmitgliedschaft verfügbar sind, und Sie müssen möglicherweise in drei Schritten zwischen verschiedenen Zielen wechseln ( siehe Abbildung unten)

Geben Sie hier die Bildbeschreibung ein

Sultan Ali
quelle
0

Ich musste sicherstellen, dass dieses Kontrollkästchen Allgemeine Tests aktiviert war Dieses Kontrollkästchen Allgemeine Tests wurde aktiviert

zeichnete ..
quelle