Vérifier un export CSV iOS sur un Mac dans le cloud

Vérifier un export CSV iOS sur un Mac dans le cloud

Une équipe exporte des données d’une application iOS au format CSV, puis lance les tests sur un nœud de build distant. Les tests unitaires indiquent que « le fichier a été écrit », mais un collègue constate à l’ouverture qu’une note s’étend sur deux lignes et qu’une autre saisie utilisateur est devenue une formule dans le tableur. Ce qu’il faut valider ici, ce n’est pas le bouton de partage, mais les octets exportés, les limites des champs et le traitement des entrées dangereuses. Un Mac dans le cloud permet d’intégrer ces vérifications, qui ne nécessitent aucune interaction manuelle, à chaque build.

Définir d’abord le contrat d’export

Cet article s’appuie sur un contrat textuel précis : sortie en UTF-8, sans marque d’ordre des octets ; chaque champ est entouré de guillemets doubles, et tout guillemet double contenu dans un champ est doublé ; les enregistrements sont séparés par CRLF, également présent à la fin du fichier. Les sauts de ligne à l’intérieur d’un champ sont conservés tels quels : il ne faut donc pas découper tout le CSV aux sauts de ligne pour compter les enregistrements.

Une règle de sécurité s’y ajoute : si un champ de saisie libre commence par =, +, - ou @ après suppression des espaces et caractères de contrôle en début et en fin de valeur, l’export échoue. Cette règle rejette aussi certains nombres négatifs qui pourraient être de simples textes. Si le produit a réellement besoin de colonnes numériques, il faut analyser et valider leurs valeurs selon la définition de chaque colonne, et non assouplir la règle pour toutes les colonnes textuelles. L’en-tête passe par la même fonction d’encodage afin d’éviter deux logiques d’échappement distinctes.

Les guillemets CSV délimitent les champs ; ils ne contrôlent pas l’interprétation des formules par un tableur. Préfixer un texte d’une apostrophe ne produit pas nécessairement le même résultat selon l’outil d’importation. Le refus des entrées dangereuses est plus facile à définir et à tester, mais le comportement de l’outil d’importation cible doit tout de même être vérifié.

Produire des octets vérifiables avec une seule fonction

Placez la logique d’encodage dans un fichier Swift accessible à la fois à l’application et à la cible de test. La fonction ci-dessous reçoit un tableau de tableaux de chaînes ; dans l’application, vous pouvez d’abord convertir les modèles métier en colonnes dans l’ordre voulu. trimmingCharacters ne sert qu’à détecter le risque : la valeur d’origine est conservée dans la sortie, sans modification silencieuse des données utilisateur.

import Foundation

enum CSVExportError: Error {
    case unsafeValue
}

func csvField(_ value: String) throws -> String {
    let ignored = CharacterSet.whitespacesAndNewlines
        .union(.controlCharacters)
    let probe = value.trimmingCharacters(in: ignored)

    if let first = probe.first, "=+-@".contains(first) {
        throw CSVExportError.unsafeValue
    }

    let escaped = value.replacingOccurrences(of: "\"", with: "\"\"")
    return "\"" + escaped + "\""
}

func csvDocument(_ rows: [[String]]) throws -> Data {
    let lines = try rows.map { row in
        try row.map(csvField).joined(separator: ",")
    }
    return Data((lines.joined(separator: "\r\n") + "\r\n").utf8)
}

Le code appelant doit aussi vérifier que chaque ligne comporte le même nombre de colonnes que l’en-tête, décider si un jeu de données vide produit uniquement l’en-tête ou déclenche un message à l’utilisateur, et transformer les erreurs en messages visibles. Ne capturez pas unsafeValue pour exporter ensuite la valeur d’origine sans contrôle : les tests passeraient, tandis que le fichier réellement partagé contournerait la règle. Si l’export contient des données sensibles, vérifiez également le fichier de destination et ses droits d’accès avant le partage.

Tester le cas valide et le cas refusé

Les tests doivent comparer la sortie réelle, et pas seulement vérifier que l’objet Data n’est pas vide. Le premier exemple ci-dessous couvre la virgule, les guillemets, les caractères chinois et le saut de ligne dans un champ ; le second vérifie qu’une tabulation initiale ne permet pas de contourner le contrôle.

import XCTest

final class CSVExportTests: XCTestCase {
    func testQuotingAndLineEndings() throws {
        let data = try csvDocument([
            ["name", "note"],
            ["张三", "甲,\"乙\"\n丙"]
        ])

        XCTAssertEqual(
            String(decoding: data, as: UTF8.self),
            "\"name\",\"note\"\r\n\"张三\",\"甲,\"\"乙\"\"\n丙\"\r\n"
        )
    }

    func testFormulaLikeTextIsRejected() {
        for value in ["=1+1", "\t@SUM(A1:A2)", "+1", " -2"] {
            XCTAssertThrowsError(try csvDocument([[value]]))
        }
    }
}

Ces tests démontrent que la sortie respecte le format convenu et que la règle de refus fonctionne. Ils ne prouvent pas que tous les tableurs importeront le fichier comme prévu. Les sauts de ligne entre guillemets, les textes ressemblant à des dates et les caractères non ASCII, en particulier, doivent encore être vérifiés avec la méthode d’importation utilisée par les destinataires du fichier.

Conserver des résultats vérifiables sur le nœud de build

Sur le Mac dans le cloud, vérifiez d’abord que les outils en ligne de commande de Xcode sont disponibles, puis lancez les tests avec un scheme déjà partagé dans le projet. Remplacez les noms de l’exemple par ceux du dépôt ; avant l’exécution, vous pouvez vérifier les points d’entrée avec xcodebuild -list -project App.xcodeproj. La cible de test doit accéder directement au fichier d’encodage, sans qu’il soit nécessaire d’en copier l’implémentation pour les tests.

xcodebuild test \
  -project App.xcodeproj \
  -scheme App \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -only-testing:AppTests/CSVExportTests \
  -resultBundlePath "$PWD/CSVExport.xcresult"

Le nom du simulateur doit correspondre à un environnement d’exécution installé sur ce nœud ; vérifiez-le au préalable avec xcrun simctl list devices available. Le chemin CSVExport.xcresult ne doit pas déjà exister au lancement. En cas d’exécutions répétées, attribuez un répertoire distinct à chaque tâche pour éviter que d’anciens résultats n’interfèrent. Ce test ne vérifie que l’encodage en Swift pur et ne dépend pas de l’interface graphique. Si le parcours de partage doit aussi être validé, préparez séparément la session et les autorisations nécessaires aux tests d’interface.

Passer des données de test au fichier réel

Une fois les tests unitaires réussis, générez un échantillon fixe par le véritable parcours d’export de l’application. Il doit contenir au minimum un champ vide, une virgule, des guillemets doubles, un saut de ligne dans un champ et des caractères chinois. Vérifiez que le fichier provient bien du même csvDocument, et non d’un nouvel assemblage de chaînes dans la couche de partage. Lors de l’enregistrement dans un emplacement accessible en écriture par l’application, Data.write(to:options: .atomic) peut éviter la lecture d’un fichier partiellement écrit. La suppression des fichiers temporaires après un échec du partage doit elle aussi être testée selon le parcours de l’application.

Ne concluez pas à la réussite en comptant simplement les « lignes » dans le terminal : un champ entre guillemets peut contenir un saut de ligne. Il est plus fiable d’importer l’échantillon et de comparer les champs un par un, puis de vérifier l’ordre des colonnes, la dernière ligne et les messages d’erreur. Si l’outil d’importation convertit de lui-même un identifiant en nombre ou un texte ressemblant à une date en date, cela relève d’un autre contrat de format. Il faut alors définir explicitement les types de colonnes ou choisir un format d’échange adapté, sans prétendre que les guillemets CSV figent le type des cellules.

Liste de vérification avant la fusion

Les défauts les plus difficiles à repérer dans un export CSV se cachent souvent non pas dans les exemples ordinaires, mais dans une note saisie par un véritable utilisateur. Transformer d’abord ces cas limites en tests fixes, puis contrôler le fichier final, est bien plus fiable qu’un examen visuel après sa création.

Questions fréquentes

Faut-il échapper les guillemets à l’intérieur d’un champ CSV déjà délimité ?

Oui. Chaque guillemet interne doit être doublé, puis le champ entier doit être entouré de guillemets doubles.

Une apostrophe initiale neutralise-t-elle les formules dans tous les tableurs ?

Non. Le comportement varie selon l’importateur. L’exemple refuse les textes suspects et prévoit un contrôle distinct dans l’outil de destination.

UDPMac Mac dans le cloud

Lancez votre prochaine compilation Xcode sur un Mac physique dédié.

Choisissez votre modèle Apple Silicon, votre région et la durée de location, puis exécutez votre propre processus de compilation à distance. Les disponibilités sont celles indiquées dans la console au moment de la commande.

Voir les offres de location de Mac dans le cloud