Articles techniques CDNMac

Tester les types d’entrée et les fichiers temporaires d’une extension de partage iOS

Tester les types d’entrée et les fichiers temporaires d’une extension de partage iOS

Un utilisateur partage une image depuis Photos vers une application iOS. L’extension de partage apparaît bien dans le menu, mais affiche « Impossible de lire le fichier » lorsqu’il l’ouvre. Vérifier que l’extension compile ne suffit pas à diagnostiquer ce problème : l’application source fournit un NSItemProvider, qui peut déclarer plusieurs types, et un fichier peut n’être accessible à un emplacement temporaire que pendant le rappel de chargement. Pour trouver où la chaîne se rompt, il faut valider séparément l’affichage de l’extension, la lecture du contenu et sa transmission à l’application principale.

Définir d’abord le contrat d’entrée

Recensez les sources et les données que le produit doit réellement prendre en charge, au lieu de regrouper « toutes les images » dans un test imprécis. Notez au minimum l’application source, le contenu partagé, les registeredTypeIdentifiers, la méthode de lecture attendue et le résultat que doit finalement recevoir l’application principale. Une même image partagée depuis des sources différentes peut être fournie sous forme de fichier, de données ou des deux. L’extension ne doit pas se fier uniquement au premier type du tableau.

Entrée Ce que l’extension doit vérifier Critère de validation
Une image Présence d’une représentation conforme à un type image Le contenu est lisible et un enregistrement de transmission est créé
Plusieurs images Types déclarés pour chaque pièce jointe Le nombre d’éléments correspond à la somme des réussites et des échecs
Texte brut Présence d’une représentation textuelle Le texte est accepté selon les besoins du produit ou explicitement refusé
Contenu non pris en charge Aucun type correspondant Une erreur compréhensible s’affiche, sans créer d’enregistrement vide

La règle d’activation de l’extension détermine quand elle apparaît dans le menu ; la vérification des types à l’exécution détermine si elle peut traiter les pièces jointes reçues. Il faut tester les deux : voir l’extension dans le menu ne prouve pas que la lecture fonctionne.

Examiner les types déclarés avant de choisir une représentation

Pendant le développement, consignez les identifiants de type des pièces jointes dans des diagnostics locaux, sans transmettre le contenu de l’utilisateur avec eux. Pour choisir un type, vérifiez sa conformité au type recherché plutôt que de coder en dur l’identifiant complet fourni par une application source donnée. Si le produit n’accepte que les images, prévoyez un refus explicite pour le texte ; n’essayez pas de décoder un contenu inconnu comme une image.

Copier le fichier pendant le rappel

Pour une représentation sous forme de fichier, l’erreur la plus risquée consiste à conserver l’URL renvoyée par loadFileRepresentation, puis à demander à l’application principale de l’ouvrir une fois l’extension terminée. Cette URL désigne un fichier temporaire fourni pendant le chargement : ce n’est pas un emplacement de transmission durable.

Le critère de validation n’est pas « le chemin s’affiche dans le rappel », mais « après le rappel, l’application principale peut toujours lire une copie à laquelle elle est autorisée à accéder ».

L’extrait Swift suivant montre la frontière essentielle pour un fichier image. Si l’extension et l’application principale doivent partager des fichiers, configurez-les avec le même App Group et fournissez l’identifiant du groupe dans la configuration de déploiement ; ne copiez pas un identifiant d’exemple directement dans le projet. La copie reçoit un nouveau nom de fichier pour ne pas écraser le résultat d’un autre partage.

import Foundation
import UniformTypeIdentifiers

enum ShareCopyError: Error {
    case unsupportedType
    case containerUnavailable
    case missingFile
}

func copySharedImage(
    from provider: NSItemProvider,
    groupID: String,
    completion: @escaping (Result<URL, Error>) -> Void
) {
    guard let typeID = provider.registeredTypeIdentifiers.first(where: {
        UTType($0)?.conforms(to: .image) == true
    }) else {
        completion(.failure(ShareCopyError.unsupportedType))
        return
    }

    provider.loadFileRepresentation(forTypeIdentifier: typeID) { source, error in
        if let error {
            completion(.failure(error))
            return
        }
        guard let source else {
            completion(.failure(ShareCopyError.missingFile))
            return
        }
        guard let container = FileManager.default
            .containerURL(forSecurityApplicationGroupIdentifier: groupID) else {
            completion(.failure(ShareCopyError.containerUnavailable))
            return
        }

        do {
            let directory = container.appendingPathComponent(
                "Incoming", isDirectory: true
            )
            try FileManager.default.createDirectory(
                at: directory, withIntermediateDirectories: true
            )
            let ext = UTType(typeID)?.preferredFilenameExtension ?? "bin"
            let target = directory.appendingPathComponent(
                UUID().uuidString + "." + ext
            )
            try FileManager.default.copyItem(at: source, to: target)
            completion(.success(target))
        } catch {
            completion(.failure(error))
        }
    }
}

Ce code ne traite qu’une seule image en pièce jointe. Une implémentation complète doit aussi agréger les résultats de plusieurs pièces jointes, limiter la taille des fichiers acceptés et décider, en cas de réussite partielle, s’il faut conserver les fichiers déjà copiés ou annuler l’ensemble. Transmettez à l’application principale l’identifiant d’un fichier situé dans un répertoire contrôlé. Après lecture, elle doit supprimer les copies selon les règles du produit, sans les accumuler indéfiniment.

Tester automatiquement la frontière de lecture

Commencez par tester les fonctions de sélection du type et de copie, puis l’interface de partage complète. Couvrez au minimum un type image, un type non pris en charge, une erreur de chargement, l’indisponibilité du conteneur partagé, l’échec partiel de plusieurs pièces jointes et l’absence d’écrasement entre entrées portant le même nom. Le substitut utilisé dans les tests doit permettre de contrôler le moment du rappel : fournissez le fichier pendant celui-ci, retirez le fichier source après son retour, puis vérifiez que la copie reste lisible. Le test porte ainsi sur la durée de vie du fichier, et pas seulement sur une URL non nulle.

Sur un Mac cloud CDNMac, consultez d’abord les schemes exposés par le projet et les simulateurs disponibles, puis lancez les tests correspondants. Les commandes ci-dessous supposent que les trois variables d’environnement contiennent les valeurs réelles du projet en cours. Le paquet de résultats est placé dans un nouveau répertoire pour éviter tout conflit lors d’une nouvelle exécution.

: "${PROJECT:?set PROJECT to an existing .xcodeproj path}"
: "${SCHEME:?set SCHEME to a shared test scheme}"
: "${SIMULATOR_ID:?set SIMULATOR_ID to an available device ID}"

xcodebuild -list -project "$PROJECT"
xcrun simctl list devices available

RESULT_ROOT="$(mktemp -d)"
xcodebuild -project "$PROJECT" \
  -scheme "$SCHEME" \
  -destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
  -resultBundlePath "$RESULT_ROOT/share-input.xcresult" \
  test

La CI doit conserver le paquet de résultats et se fier au code de sortie du processus de test pour déterminer si la tâche a réussi. Ne cherchez pas seulement « passed » dans les journaux : un échec au démarrage de l’extension ou l’absence des tests visés dans le scheme doit aussi être détecté. Lors de la première intégration, faites volontairement échouer une assertion pour vérifier que le pipeline bloque effectivement la livraison.

Combler les limites des tests avec de vrais partages

Les tests unitaires valident votre logique de lecture, mais ne garantissent pas les représentations que fournira une application source. Sur un appareil de test, partagez du contenu depuis chaque source prévue : vérifiez d’abord que l’extension apparaît, puis examinez les types réellement déclarés, le résultat de la lecture et la copie accessible dans l’application principale. Pour les grandes images et les sélections multiples, observez notamment l’utilisation de la mémoire, les indications d’attente et le comportement en cas d’annulation. Une seule petite image traitée avec succès ne suffit pas à conclure la validation.

Pour diagnostiquer un échec, procédez dans l’ordre : si l’extension n’apparaît pas, vérifiez la règle d’activation ; si elle apparaît mais qu’aucun type ne correspond, relevez les identifiants fournis par la source ; si le chargement échoue, conservez la catégorie d’erreur ; si la lecture réussit dans l’extension mais échoue dans l’application principale, vérifiez les autorisations App Group, la copie pendant le rappel et le fait que le chemin transmis pointe bien vers la copie. Limitez les relevés de test aux types, aux étapes et aux erreurs nécessaires, sans inscrire le contenu partagé dans les journaux de compilation.

Vérifications avant livraison

Avant publication, contrôlez quatre points : les types déclarés par l’extension correspondent à ceux réellement pris en charge ; chaque fichier est copié pendant son rappel de chargement ; l’application principale ne lit que des copies situées dans un répertoire autorisé ; les refus, annulations et échecs partiels produisent un résultat visible. Si la configuration App Group a changé, répétez aussi le parcours de transmission sur un appareil de test avec une installation neuve : un conteneur déjà présent sur la machine de développement ne suffit pas à le valider.

Conservez la matrice des types, les résultats automatisés et les relevés de partage sur appareil dans la même modification. Lorsqu’un nouveau format d’entrée sera ajouté, le point de départ sera clair : compléter d’abord le contrat et les tests d’échec, puis implémenter la lecture, plutôt que d’attendre qu’un utilisateur signale l’absence de fichier après un partage pour deviner ce que la source a fourni.

Questions fréquentes

Peut-on transmettre directement à l’application l’URL reçue par l’extension ?

Non. Copiez le fichier pendant le rappel de loadFileRepresentation vers un emplacement lisible par l’application, puis transmettez l’identifiant de cette copie.

Que faut-il vérifier sur appareil après les tests en simulateur ?

Ouvrez le partage depuis l’application source et vérifiez la présence de l’extension, le type fourni, le traitement d’un gros fichier et la lecture de la copie par l’application.

CDNMac M4

Lancez votre prochain build iOS sur un Mac dédié dans le cloud

Choisissez une configuration fixe avec M4, 16 Go de RAM et un SSD de 256 Go, parmi six nœuds et quatre durées de location, selon vos besoins.

Louer maintenant