Pourquoi le code à l'intérieur des tests unitaires ne peut-il pas trouver des ressources groupées?


184

Certains codes que je suis en test unitaire doivent charger un fichier de ressources. Il contient la ligne suivante:

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

Dans l'application, il fonctionne très bien, mais lorsqu'il est exécuté par le cadre de test unitaire, il pathForResource:renvoie nil, ce qui signifie qu'il n'a pas pu le localiser foo.txt.

Je me suis assuré que cela foo.txtétait inclus dans la phase de création de Copy Bundle Resources de la cible de test unitaire, alors pourquoi ne trouve-t-il pas le fichier?

Réponses:


316

Lorsque le faisceau de tests unitaires exécute votre code, votre bundle de tests unitaires n'est PAS le bundle principal.

Même si vous exécutez des tests, pas votre application, votre bundle d'applications reste le bundle principal. (Vraisemblablement, cela empêche le code que vous testez de rechercher le mauvais bundle.) Ainsi, si vous ajoutez un fichier de ressources au bundle de test unitaire, vous ne le trouverez pas si vous recherchez le bundle principal. Si vous remplacez la ligne ci-dessus par:

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

Ensuite, votre code recherchera le bundle dans lequel se trouve votre classe de test unitaire, et tout ira bien.


Ça ne marche pas pour moi. Toujours le bundle de build et non le bundle de test.
Chris

1
@Chris Dans la ligne d'exemple, je suppose que selffait référence à une classe dans le bundle principal, pas à la classe de cas de test. Remplacez [self class]par n'importe quelle classe de votre bundle principal. Je vais éditer mon exemple.
benzado

@benzado Le bundle est toujours le même (build), ce qui est correct je pense. Parce que lorsque j'utilise self ou AppDelegate, les deux sont situés dans le bundle principal. Lorsque je vérifie les phases de construction de la cible principale, les deux fichiers se trouvent. Mais ce que je veux différer entre le paquet principal et le paquet de test au moment de l'exécution. Le code où j'ai besoin du bundle se trouve dans le bundle principal. J'ai le problème suivant. Je charge un fichier png. Normalement, ce fichier ne fait pas partie du bundle principal car l'utilisateur le télécharge à partir d'un serveur. Mais pour un test, je souhaite utiliser un fichier du bundle de test sans le copier dans le bundle principal.
Chris

2
@Chris J'ai fait une erreur avec ma précédente modification et j'ai à nouveau modifié la réponse. Au moment du test, le bundle d'applications est toujours le bundle principal. Si vous souhaitez charger un fichier de ressources qui se trouve dans le bundle de tests unitaires, vous devez l'utiliser bundleForClass:avec une classe du bundle de tests unitaires. Vous devriez obtenir le chemin du fichier dans votre code de test unitaire, puis transmettre la chaîne de chemin à votre autre code.
benzado

Cela fonctionne, mais comment puis-je faire la distinction entre un déploiement d'exécution et un déploiement test? Sur la base du fait que s'il s'agit d'un test, j'ai besoin d'une ressource du bundle de test dans une classe du bundle principal. S'il s'agit d'un «run» régulier, j'ai besoin d'une ressource du bundle principal et non du bundle de test. Une idée?
Chris

80

Une implémentation Swift:

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)

Bundle fournit des moyens de découvrir les chemins principaux et de test de votre configuration:

@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

Dans Xcode 6 | 7 | 8 | 9, un chemin de bundle de test unitaire sera dans Developer/Xcode/DerivedDataquelque chose comme ...

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

... qui est distinct du Developer/CoreSimulator/Devices chemin du bundle régulier (non-test unitaire) :

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

Notez également que l'exécutable du test unitaire est, par défaut, lié au code de l'application. Cependant, le code de test unitaire ne doit avoir l'appartenance cible que dans l'ensemble de test. Le code d'application ne doit avoir l'appartenance cible que dans l'ensemble d'applications. Lors de l'exécution, le bundle cible de test unitaire est injecté dans le bundle d'application pour exécution .

Gestionnaire de paquets Swift (SPM) 4:

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

Remarque: Par défaut, la ligne de commande swift testcréera un MyProjectPackageTests.xctestensemble de test. Et, le swift package generate-xcodeprojcréera un MyProjectTests.xctestbundle de test. Ces différents lots de tests ont des chemins différents . En outre, les différents ensembles de tests peuvent présenter des différences de structure de répertoire interne et de contenu .

Dans les deux cas, .bundlePathet .bundleURLrenverra le chemin du bundle de test en cours d'exécution sur macOS. Cependant, Bundlen'est actuellement pas implémenté pour Ubuntu Linux.

En outre, la ligne de commande swift buildet swift testne fournissent actuellement pas de mécanisme de copie des ressources.

Cependant, avec un peu d'effort, il est possible de configurer des processus pour utiliser Swift Package Manager avec des ressources dans les environnements de ligne de commande macOS Xcode, macOS et Ubuntu. Un exemple peut être trouvé ici: 004.4'2 SW Dev Swift Package Manager (SPM) With Resources Qref

Voir aussi: Utiliser des ressources dans des tests unitaires avec Swift Package Manager

Gestionnaire de paquets Swift (SPM) 4.2

Swift Package Manager PackageDescription 4.2 introduit la prise en charge des dépendances locales .

Les dépendances locales sont des packages sur disque qui peuvent être référencés directement en utilisant leurs chemins. Les dépendances locales ne sont autorisées que dans le package racine et elles remplacent toutes les dépendances portant le même nom dans le graphique du package.

Remarque: je m'attends, mais je n'ai pas encore testé, que quelque chose comme ce qui suit devrait être possible avec 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
Pour Swift 4 également, vous pouvez utiliser Bundle (for: type (of: self))
Rocket Garden

14

Avec Swift Swift 3, la syntaxe self.dynamicTypeest obsolète, utilisez-la à la place

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

ou

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

4

Confirmez que la ressource est ajoutée à la cible de test.

entrez la description de l'image ici


2
L'ajout de ressources au groupe de tests rend les résultats du test largement invalides. Après tout, une ressource pourrait facilement se trouver dans la cible de test mais pas dans la cible de l'application, et vos tests réussiraient tous, mais l'application s'enflammerait.
dgatwood

1

si vous avez plusieurs cibles dans votre projet, vous devez ajouter des ressources entre différentes cibles disponibles dans l' appartenance cible et vous devrez peut-être basculer entre différentes cibles en 3 étapes illustrées dans la figure ci-dessous

entrez la description de l'image ici


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.