Pour donner à chaque scénario son propre résultat et son propre contrôle de relance dans Xcode, remplacez un test unitaire XCTest basé sur une boucle par le @Test paramétré de Swift Testing. Ce guide utilise un calcul local de disposition de tuiles pour illustrer la migration et vérifier son comportement dans les rapports.

Pourquoi une boucle XCTest ne présente-t-elle pas chaque scénario séparément ?

XCTest découvre les méthodes de test d’une sous-classe de XCTestCase. Ces méthodes ne prennent aucun paramètre et leur nom commence par test. Une boucle à l’intérieur d’une méthode découverte fait toujours partie de cette méthode ; ses itérations ne deviennent pas des tests découverts séparément. Ajouter le nom d’un scénario à une assertion peut améliorer les diagnostics, mais ne crée pas de commande d’exécution individuelle. Voir la documentation Apple Defining Test Cases and Test Methods.

Le cycle de vie suit la même limite. XCTest exécute les méthodes de configuration de l’instance avant chaque méthode de test et les méthodes de nettoyage après celle-ci. Une boucle de cinq itérations ne bénéficie donc pas automatiquement de cinq cycles de configuration et de nettoyage. Si une itération modifie un objet de test, la suivante risque d’hériter de cet état, à moins que le test ne le réinitialise explicitement. Apple décrit cette séquence dans Set Up and Tear Down State in Your Tests.

Swift Testing fournit le résultat recherché ici : une fonction accepte des arguments de scénario, et l’exécuteur traite ses appels comme des cas indépendants. Xcode affiche leurs arguments et propose des commandes d’exécution individuelles. Apple illustre ce comportement dans Go further with Swift Testing.

Cette distinction compte lors de l’analyse des échecs. L’échec d’une assertion et une sortie anticipée sont deux événements différents : une boucle peut continuer les vérifications après une assertion ordinaire, tandis qu’une erreur levée ou un return explicite peut empêcher l’exécution des itérations suivantes. Séparer les scénarios en cas distincts élimine ce flux de contrôle partagé entre eux. Cela ne permet pas de récupérer d’un échec qui termine le processus.

Préparer la cible de tests unitaires existante

Utilisez Xcode 16 ou une version ultérieure pour ce flux de travail. Swift Testing est inclus dans ces distributions de Xcode et dans les chaînes d’outils de Swift.org à partir de Swift 6.0. Le projet Swift recommande d’utiliser la version intégrée pour les tâches de test courantes ; cet exemple ne nécessite aucune dépendance à un package externe. Voir Obtaining Swift Testing.

Conservez le bundle de tests unitaires existant. Apple prend explicitement en charge Swift Testing et XCTest dans la même cible ; une migration progressive peut donc commencer par un nouveau fichier Swift. Dans Xcode, choisissez File > New > New File From Template, sélectionnez Swift Testing Unit Test, puis ajoutez le fichier à la cible de tests unitaires existante. Si le projet n’a pas de cible de test, créez une Unit Testing Bundle via File > New > Target. Ces étapes sont décrites dans Adding tests to your Xcode project.

Ce guide teste directement le code de calcul. Il n’automatise pas une fenêtre de bureau. La méthode documentée par Apple pour l’automatisation de l’interface utilisateur continue de s’appuyer sur XCTest ; utilisez cette approche paramétrée pour la logique sous-jacente qui détermine la disposition. Le même guide d’ajout de tests décrit cette limite entre les frameworks.

Faire de chaque scénario un enregistrement d’arguments complet

Supposons qu’une app Mac dispose des tuiles en rangées. Avec quatre colonnes, zéro tuile nécessite zéro rangée, quatre tuiles remplissent une rangée et cinq tuiles en nécessitent deux. La tâche consiste à conserver ces vérifications tout en rendant chaque scénario visible et exécutable séparément.

Utilisez un seul enregistrement contenant le nom du scénario, les entrées et le résultat attendu. Ainsi, le résultat attendu reste associé à son entrée lorsque des cas sont ajoutés ou réordonnés. Les cinq enregistrements suivants sont des données de test illustratives, et non des observations provenant d’une application existante.

Scénario Nombre de tuiles Colonnes Rangées attendues Objectif
empty 0 4 0 Entrée vide
one-tile 1 4 1 Première rangée incomplète
exact-row 4 4 1 Limite exacte
wrap 5 4 2 Première tuile au-delà de la limite
two-full-rows 8 4 2 Multiple exact

Placez ce fichier complet et autonome dans la cible de tests unitaires. Il inclut une implémentation synthétique afin de pouvoir être essayé sans module d’app ni autre fichier. Il n’utilise aucun service, mock, fichier ni accès réseau.

import Testing

private struct TileLayout {
    func rowCount(tileCount: Int, columns: Int) -> Int {
        precondition(tileCount >= 0)
        precondition(columns > 0)

        let fullRows = tileCount / columns
        let partialRow = tileCount % columns == 0 ? 0 : 1
        return fullRows + partialRow
    }
}

private struct LayoutScenario:
    Codable, Sendable, CustomTestStringConvertible {
    let name: String
    let tileCount: Int
    let columns: Int
    let expectedRows: Int

    var testDescription: String { name }

    static let examples: [LayoutScenario] = [
        .init(name: "empty", tileCount: 0,
              columns: 4, expectedRows: 0),
        .init(name: "one-tile", tileCount: 1,
              columns: 4, expectedRows: 1),
        .init(name: "exact-row", tileCount: 4,
              columns: 4, expectedRows: 1),
        .init(name: "wrap", tileCount: 5,
              columns: 4, expectedRows: 2),
        .init(name: "two-full-rows", tileCount: 8,
              columns: 4, expectedRows: 2)
    ]
}

struct TileLayoutTests {
    @Test("Tile rows", arguments: LayoutScenario.examples)
    fileprivate func rowCountMatchesScenario(
        _ scenario: LayoutScenario
    ) {
        let layout = TileLayout()
        let actualRows = layout.rowCount(
            tileCount: scenario.tileCount,
            columns: scenario.columns
        )
        #expect(actualRows == scenario.expectedRows)
    }
}

La migration déplace l’itération dans @Test(arguments:) : chaque élément devient l’argument d’un appel. Codable fournit le support d’encodage nécessaire aux relances sélectives, tandis que CustomTestStringConvertible fournit une étiquette de scénario lisible. Apple décrit la paramétrisation dans Implementing parameterized tests et les descriptions d’arguments personnalisées dans Go further with Swift Testing.

L’objet de test est volontairement créé dans la fonction de test. Chaque appel crée son propre TileLayout ; rien ne dépend d’un scénario antérieur. Si vous migrez un objet de test existant, déplacez sa configuration délibérément au lieu de vous attendre à ce que les méthodes héritées de XCTest soient exécutées. Les suites Swift Testing n’héritent pas de XCTestCase ; Apple indique de remplacer la configuration par un initialiseur et le nettoyage d’une classe ou d’un acteur par deinit dans Migrating a test from XCTest.

Le calcul accepte des nombres de tuiles non négatifs et des nombres de colonnes strictement positifs. Ses préconditions définissent ce contrat ; les valeurs non valides ne font pas partie des cas de cet exemple. Si le code réel de l’application doit renvoyer une erreur récupérable pour une entrée non valide, modifiez ce contrat d’API et ajoutez les tests correspondants avant d’étendre ce tableau.

Confirmer les résultats séparés et relancer un cas en échec

Lancez la fonction paramétrée à l’aide du losange placé à côté de sa déclaration. Ouvrez ensuite le Test navigator et développez la fonction pour examiner ses cas. Pour exécuter un seul cas, pointez sur le losange de ce cas et cliquez sur son bouton de lecture. Cliquer sur le bouton de lecture de la fonction parente exécute tous ses cas. Apple décrit ces deux actions dans Running tests and interpreting results.

Pour vérifier les rapports de manière contrôlée, remplacez temporairement la valeur 2 de expectedRows dans l’enregistrement wrap par 3. Exécutez toute la fonction paramétrée. Le résultat attendu est l’exécution de cinq cas, avec l’échec de la comparaison wrap, car l’implémentation renvoie 2. Sélectionnez ce cas et relancez-le individuellement, puis rétablissez la valeur attendue 2. Il s’agit d’une modification délibérée des données de test, et non de la preuve d’un défaut d’implémentation.

Examinez les détails Results et Arguments de l’attente en échec pour comparer la valeur calculée aux entrées du scénario. Pour l’exécution complète, ouvrez View > Navigators > Reports, sélectionnez l’action de test et consultez sa section Tests. Ces commandes d’inspection sont décrites dans le guide des résultats de test d’Apple.

L’exécution sélective nécessite une condition supplémentaire aux étiquettes lisibles : l’exécuteur doit pouvoir faire correspondre chaque argument de manière déterministe. La documentation actuelle d’Apple accepte CustomTestArgumentEncodable, certains types RawRepresentable, Encodable, ou Identifiable avec un identifiant encodable. L’enregistrement Codable de l’exemple satisfait à l’option d’encodage. Une chaîne d’affichage personnalisée seule ne satisfait pas à cette exigence. Voir Implementing parameterized tests.

Les calculs attendus pour les cinq enregistrements ont été vérifiés localement avec la division entière et le reste en Python. Une implémentation incorrecte illustrative qui utilise uniquement la division entière arrondie à l’inférieur échoue pour one-tile et wrap. Le fichier Swift a été examiné, mais pas compilé dans cet environnement de recherche, car Swift n’y était pas disponible ; l’exécution dans Xcode décrite plus haut reste l’étape de vérification exécutable.

Préserver le nombre de cas et le cycle de vie visés

Une vérification de migration utile compare trois éléments : le nombre d’enregistrements de scénario, les cas exécutés lors de l’exécution complète et la possibilité de lancer seul un cas sélectionné. Dans cet exemple, le nombre attendu est cinq. Un résultat différent devrait inciter à examiner la construction des arguments et la sélection des tests avant de modifier le calcul.

Gardez ensemble les attentes appariées. Passer deux collections distinctes à arguments: génère leur produit cartésien. Cinq entrées et cinq résultats attendus produiraient donc 25 combinaisons, au lieu de cinq scénarios appariés. Utilisez des collections distinctes lorsque chaque combinaison est pertinente ; utilisez des enregistrements ou zip() pour les données appariées. Apple explique cette distinction dans Implementing parameterized tests.

Si vous utilisez zip(), vérifiez la longueur des collections avant de construire les paires : Swift s’arrête à la séquence la plus courte. Un résultat attendu supplémentaire pourrait donc rester inutilisé sans générer de cas. Un seul enregistrement de scénario évite ce type de décalage. Ce comportement de troncature est décrit dans la référence Swift de zip(::).

L’ordre d’exécution influe également sur la décision de migration. Swift Testing exécute les cas en parallèle par défaut. Cet exemple utilise des arguments immuables et un objet de test local ; ses résultats ne dépendent donc pas de l’ordre. Pour une fonction paramétrée qui doit temporairement s’exécuter en série, ajoutez .serialized à son attribut @Test. Appliquer ce trait à une suite sérialise également les tests et cas qu’elle contient, mais ne sérialise pas les suites sans lien. Voir Running tests serially or in parallel.

L’exécution en série ne crée pas un état neuf ni un contrat de dépendance entre scénarios. Si un cas ne réussit qu’après la modification d’un élément par un autre cas, repensez sa configuration pour qu’il puisse s’exécuter indépendamment. Si les étapes forment intentionnellement une opération continue, conservez-les dans un seul test et paramétrez plutôt des opérations complètes.

Utilisez les assertions de Swift Testing dans tout cet exemple migré. Si vous réutilisez des helpers qui contiennent des assertions XCTest, vérifiez le comportement d’interopérabilité de la chaîne d’outils : le guide de migration actuel d’Apple décrit des modes et des valeurs par défaut qui varient selon la chaîne d’outils et la version des outils de package. Garder ce fichier entièrement dans Testing évite cette dépendance. Voir Migrating a test from XCTest.

Si la migration doit attendre, l’alternative XCTest prise en charge consiste à créer une méthode de test nommée et sans paramètre pour chaque scénario, avec des helpers de calcul et d’assertion partagés si nécessaire. Apple recommande un test pour chaque scénario dans son guide d’ajout de tests. Cela préserve des résultats distincts par méthode sans nécessiter la paramétrisation de Swift Testing.

La conversion est terminée lorsque chaque scénario d’origine correspond à un cas exécuté, que son objet de test commence dans l’état voulu et qu’un cas sélectionné peut être exécuté seul avec succès. Conservez les entrées limites et les résultats attendus lors de cette modification : les rapports indépendants sont utiles parce qu’ils facilitent la localisation, le diagnostic et la répétition de ces vérifications existantes.

Sources