NSURL zum Dateipfad im Testpaket mit XCTest

77

Ich versuche, eine iOS-App mit TDD und dem neuen XCTest-Framework zu schreiben. Eine meiner Methoden ruft eine Datei aus dem Internet ab (mit einem NSURL-Objekt) und speichert sie in den Dokumenten des Benutzers. Die Signatur der Methode ähnelt:

- (void) fetchAndStoreImage:(NSURL *)imageUrl

Ich versuche, den Test für diese Methode so zu schreiben, dass er nicht fehlschlägt, wenn keine Verbindung zum Internet besteht. Mein Ansatz (aus einer vorherigen Frage entnommen ) besteht darin, die Methode mithilfe einer NSURL für ein Bild im lokalen Dateisystem aufzurufen.

Wenn ein neues Projekt mit aktivierten Komponententests erstellt wird, verfügt das Verzeichnis "Tests" über ein Unterverzeichnis mit dem Namen "Unterstützende Dateien". Ich nehme an, dort sollten meine Testbilder hingehen. Meine Frage ist, wie ich ein NSURL-Objekt erhalten kann, das auf ein Bild in diesem Verzeichnis verweist, da ich nicht möchte, dass Testbilder mit der Anwendung gebündelt werden. Jede Hilfe wird geschätzt.

Eduardo Arenas Prada
quelle
Diese Antwort behebt dieses Problem, ohne den Code zu ändern stackoverflow.com/a/24330617/468868
Carlos Ricardo

Antworten:

131

Tatsächlich ist der [NSBundle mainBundle]beim Ausführen eines UnitTest nicht der Pfad Ihrer App, sondern / Developer / usr / bin, sodass dies nicht funktioniert.

Der Weg, um Ressourcen in einem Unit-Test zu erhalten, ist hier: OCUnit & NSBundle

Kurz gesagt, verwenden Sie:

[[NSBundle bundleForClass:[self class]] resourcePath]

oder in deinem Fall:

[[NSBundle bundleForClass:[self class]] resourceURL]
Ben Clayton
quelle
@pshah Ja, ich glaube schon, obwohl ich es selbst nicht ausprobiert habe.
Ben Clayton
21
Das ist sehr hilfreich. Übrigens, in Swift ist esNSBundle(forClass: self.dynamicType)
Bill
50

Swift 5.3

Hinweis: Swift 5.3 enthält Funktionen für Paketmanagerressourcen SE-0271 , die mit Ressourcen für Anwendungspakete und Testpakete verwendet werden können.

Ressourcen sind nicht immer für die Verwendung durch Kunden des Pakets vorgesehen. Eine Verwendung von Ressourcen kann Testvorrichtungen umfassen, die nur für Komponententests benötigt werden. Solche Ressourcen würden nicht zusammen mit dem Bibliothekscode in Clients des Pakets integriert, sondern nur während der Ausführung der Pakettests verwendet.

Swift 4, 5:

let testBundle = Bundle(for: type(of: self))
guard let fileURL = testBundle.url(forResource: "imageName", withExtension: "png") 
  else { fatalError() }

// path approach
guard let filePath = bundle.path(forResource: "dataName", ofType: "csv")
  else { fatalError() }
let fileUrl = URL(fileURLWithPath: 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

Die Xcode-URL befindet sich in Developer/Xcode/DerivedDataetwa ...

file:///Users/
  UserName/
    Library/
      Developer/
        Xcode/
          DerivedData/
            App-qwertyuiop.../
              Build/
                Products/
                  Debug-iphonesimulator/
                    AppTests.xctest/
                      imageName.png

... die von der Developer/CoreSimulator/DevicesURL getrennt ist

file:///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 Testbündels zurück, das derzeit unter macOS ausgeführt wird. Ist Bundlederzeit jedoch nicht für Ubuntu implementiert.

Befehlszeile swift buildund swift testbieten 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

l - marc l
quelle
Vielen Dank! Beim Versuch, Ihren Code zu verwenden, sollte es URLForResource ("imageName", withExtension: "png") sein
Ciryon
@Ciryon Das Beispiel wurde jetzt auf korrigiert withExtension. Danke. withTypewäre für das Abrufen eines Pfades pathForResource("imageName", ofType: "png")anstelle einer URL.
l
Was ist self.dynamicType?
Ramis
@ Ramis selfverweist auf den beiliegenden Test class SomeCaseTests: XCTestCase {..}. Und .dynamicTypewertet den Wert des Laufzeittyps der Klasse aus. Siehe Apple Dev-Dokument und scrollen Sie zum Abschnitt Dynamic Type Expression .
l
@ l - marcl Ich verwende Xcode 7, muss es aber trotzdem verwenden. self.dynamicTypeAndernfalls wird beim Versuch, das Bundle abzurufen , eine Fehlermeldung angezeigt . Bist du sicher, dass du nur füttern kannst NSBundle(forClass:) self?
Ziewvater
14

Nur um die richtige Antwort anzuhängen, ist dies ein Beispiel, wie Sie filePath für eine Datei in Ihren UnitTests / Supporting Files erhalten:

NSString *filePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"YourFileName.json"];
XCTAssertNotNil(filePath);

Dies wird wahrscheinlich für jemanden nützlich sein.

Denis Kutlubaev
quelle
3
  1. Sie können auf Bundle-Dateien wie in jedem Ziel verweisen.
  2. Überprüfen Sie, ob die Datei in der Erstellungsphase "Bundle-Ressourcen kopieren" (Ihres Testziels) kopiert wurde.
  3. So greifen Sie auf die lokale Datei zu:

    NSURL*imageUrl=[[NSBundle mainBundle]URLForResource:@"imageName" withExtension:@"png"];
    

Sie können asynchron zugreifen und auf die Antwort warten, indem Sie Folgendes verwenden: https://github.com/travisjeffery/TRVSMonitor

Wenn Sie hinzugefügt haben: dataset1.jsonim Testziel (2):

NSString *p=[[NSBundle mainBundle] pathForResource:@"dataset1" ofType:@"json"];
NSLog(@"%@",p);

2013-10-29 15: 49: 30.547 PlayerSample [13771: 70b] WT (0): / Benutzer / bpds / Bibliothek / Anwendungsunterstützung / iPhone Simulator / 7.0 / Anwendungen / 7F78780B-684A-40E0-AA35-A3B5D8AA9DBD / PlayerSample. app.app/dataset1.json

bpds
quelle
Ich habe es versucht und es scheint nicht zu funktionieren. Anscheinend werden Bilder im Testpaket nicht in das Hauptpaket kopiert. Der richtige Weg ist in Bens Antwort: Sie müssen das Testpaket mithilfe von [NSBundle bundleForClass: [self class]] aus der Testfallklasse abrufen.
Eduardo Arenas Prada
1
Interessanterweise scheint dies zu funktionieren (zumindest für ios-Simulatorziele). Sie müssen unbedingt sicherstellen, dass Sie die Ressource zur Erstellungsphase der Kopierpaketressourcen des Testziels hinzufügen.
voidref
2

Swift Version basierend auf akzeptierter Antwort:

let url = URL(fileURLWithPath: Bundle(for: type(of: self)).path(forResource: "my", ofType: "json") ?? "TODO: Proper checking for file")
superarts.org
quelle
1

Das Problem, mit dem ich konfrontiert war, war, dass der Anwendungscode versuchte, auf das Hauptpaket für Dinge wie bundleIdentifier zuzugreifen, und da das Hauptpaket nicht mein Unit-Test-Projekt war, gab es null zurück.

Ein Hack, der sowohl in Swift 3.0.1 als auch in Objective-C funktioniert, besteht darin, eine Objective-C-Kategorie in NSBundle zu erstellen und in Ihr Unit-Test-Projekt aufzunehmen. Sie benötigen keinen Bridging-Header oder ähnliches. Diese Kategorie wird geladen. Wenn Sie jetzt im Anwendungscode nach dem Hauptpaket fragen, gibt Ihre Kategorie das Unit-Test-Paket zurück.

@interface NSBundle (MainBundle)

+(NSBundle *)mainBundle;

@end

@implementation NSBundle (Main)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+(NSBundle *)mainBundle
{
    return [NSBundle bundleForClass:[SomeUnitTest class]];
}
#pragma clang diagnostic pop

@end
odyth
quelle
0

Hier ist die Swift-Version von Xcode 7, iOS 9 usw.

let testBundle = NSBundle(forClass: self.dynamicType)
let path = testBundle.pathForResource("someImage", ofType: "jpg")
XCTAssertNotNil(path)

Hinweis: someImage.jpg muss in Ihrem Testziel enthalten sein.


Edit: Swift 5

let testBundle = Bundle(for: type(of: self))
let path = testBundle.path(forResource: "someImage", ofType: "jpg")
XCTAssertNotNil(path)
Dan Rosenstark
quelle
0

Swift 5

let testBundle = Bundle(for: self)

Die Verwendung let testBundle = Bundle(for: type(of: self)), die in einigen der obigen Antworten enthalten ist, würde nicht kompiliert und stattdessen konsistent einen Fehler des Segmentierungsfehlers erzeugt: 11 für mich.

Chris Garvey
quelle