Cartographie des tests

Cette brève introduction au mappage des tests explique comment commencer à configurer des tests dans le Projet Android Open Source (AOSP).

À propos du mappage des tests

Le mappage des tests est une approche basée sur Gerrit qui permet aux développeurs de créer des règles de test avant et après l'envoi directement dans l'arborescence source Android, et de laisser l'infrastructure de test décider des branches et des appareils à tester. Les définitions de mappage des tests sont des fichiers JSON nommés TEST_MAPPING que vous pouvez placer dans n'importe quel répertoire source.

Atest peut utiliser les fichiers TEST_MAPPING pour exécuter des tests avant l'envoi dans les répertoires associés. Avec le mappage des tests, vous pouvez ajouter le même ensemble de tests aux vérifications avant l'envoi avec une modification minimale dans l'arborescence source Android.

Voici quelques exemples :

Le mappage des tests s'appuie sur le harnais de test Trade Federation (TF) pour l'exécution des tests et la création de rapports sur les résultats.

Définir des groupes de tests

Le mappage des tests regroupe les tests avec un groupe de tests. Le nom d'un groupe de tests peut être n'importe quelle chaîne. Par exemple, presubmit peut être le nom d'un groupe de tests à exécuter lors de la validation des modifications. Et postsubmit peut être le nom des tests utilisés pour valider les builds une fois les modifications fusionnées.

Règles de script de compilation de package

Pour que le harnais de test Trade Federation exécute des modules de test pour une compilation donnée, ces modules doivent avoir un test_suites défini pour Soong ou un LOCAL_COMPATIBILITY_SUITE défini pour Make sur l'une de ces deux suites :

  • general-tests est destiné aux tests qui ne dépendent pas de fonctionnalités spécifiques à l'appareil (telles que le matériel spécifique au fournisseur que la plupart des appareils ne possèdent pas). La plupart des tests doivent se trouver dans la suite general-tests, même s'ils sont spécifiques à une ABI, à une largeur de bits ou à des fonctionnalités matérielles telles que HWASan (il existe une cible test_suites distincte pour chaque ABI), et même s'ils doivent s'exécuter sur un appareil.
  • device-tests est destiné aux tests qui dépendent de fonctionnalités spécifiques à l'appareil. En règle générale, ces tests se trouvent sous vendor/. Spécifique à l'appareil ne fait référence qu'aux fonctionnalités propres à un appareil. Cela s'applique donc aux tests JUnit ainsi qu'aux tests GTest (qui doivent généralement être marqués comme general-tests, même s'ils sont spécifiques à une ABI).

Exemples :

Android.bp: test_suites: ["general-tests"],
Android.mk: LOCAL_COMPATIBILITY_SUITE := general-tests

Configurer l'exécution des tests dans une suite de tests

Pour qu'un test s'exécute dans une suite de tests :

  • Il ne doit pas avoir de fournisseur de compilation.
  • Il doit effectuer un nettoyage une fois terminé, par exemple en supprimant tous les fichiers temporaires générés lors du test.
  • Il doit rétablir les paramètres système à leur valeur par défaut ou d'origine.
  • Il ne doit pas supposer qu'un appareil est dans un certain état, par exemple, prêt à être rooté. La plupart des tests ne nécessitent pas de privilèges root pour s'exécuter. Si un test doit nécessiter un accès root, il doit le spécifier avec RootTargetPreparer dans son AndroidTest.xml, comme dans l'exemple suivant :

    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
    

Créer des fichiers de mappage des tests

Pour le répertoire nécessitant une couverture de test, ajoutez un fichier JSON TEST_MAPPING semblable à l'exemple. Ces règles garantissent que les tests s'exécutent lors des vérifications avant l'envoi lorsque des fichiers sont modifiés dans ce répertoire ou dans l'un de ses sous-répertoires.

Suivre un exemple

Voici un exemple de fichier TEST_MAPPING (au format JSON, mais avec des commentaires) :

{
  "presubmit": [
    // JUnit test with options and file patterns.
    {
      "name": "CtsWindowManagerDeviceTestCases",
      "options": [
        {
          "include-annotation": "android.platform.test.annotations.RequiresDevice"
        }
      ],
      "file_patterns": ["(/|^)Window[^/]*\\.java", "(/|^)Activity[^/]*\\.java"]
    },
    // Device-side GTest with options.
    {
      "name" : "hello_world_test",
      "options": [
        {
          "native-test-flag": "\"servicename1 servicename2\""
        },
        {
          "native-test-timeout": "6000"
        }
      ]
    }
    // Host-side GTest.
    {
      "name" : "net_test_avrcp",
      "host" : true
    }
  ],
  "postsubmit": [
    {
      "name": "CtsDeqpTestCases",
      "options": [
        {
          // Use regex in include-filter which is supported in AndroidJUnitTest
          "include-filter": "dEQP-EGL.functional.color_clears.*"
        }
      ]
    }
  ],
  "imports": [
    {
      "path": "frameworks/base/services/core/java/com/android/server/am"
    }
  ]
}

Définir les attributs

Dans l'exemple, presubmit et postsubmit sont les noms de chaque groupe de tests. Pour en savoir plus sur les groupes de tests, consultez Définir des groupes de tests.

Vous pouvez définir le nom du module de test ou le nom du test d'intégration Trade Federation (chemin d'accès à la ressource du fichier XML de test, par exemple, uiautomator/uiautomator-demo) dans la valeur de l'attribut name. Notez que le champ name ne peut pas utiliser la classe name ni la méthode de test name. Pour limiter les tests à exécuter, utilisez des options telles que include-filter. Consultez include-filter l'exemple d'utilisation.

Le paramètre host d'un test indique si le test est un test sans appareil exécuté sur l'hôte ou non. La valeur par défaut est false, ce qui signifie que le test nécessite un appareil pour s'exécuter. Les types de tests compatibles sont HostGTest pour les binaires GTest et HostTest pour les tests JUnit.

L'attribut file_patterns vous permet de définir une liste de chaînes d'expressions régulières pour faire correspondre le chemin d'accès relatif de n'importe quel fichier de code source (par rapport au répertoire contenant le fichier TEST_MAPPING). Dans l'exemple, test CtsWindowManagerDeviceTestCases ne s'exécute avant l'envoi que lorsqu'un fichier Java commence par Window ou Activity, qui existe dans le même répertoire que le TEST_MAPPING fichier ou l'un de ses sous-répertoires. Les barres obliques inverses (\) doivent être échappées, car elles se trouvent dans un fichier JSON.

Importer des fichiers TEST_MAPPING

L'attribut imports vous permet d'inclure des tests dans d'autres fichiers TEST_MAPPING sans copier le contenu. Les fichiers TEST_MAPPING des répertoires parents du chemin importé sont également inclus. TEST_MAPPING autorise les importations imbriquées, ce qui signifie que les fichiers importés peuvent eux-mêmes importer d'autres fichiers TEST_MAPPING, et que le mappage des tests peut fusionner les tests inclus.

TEST_MAPPING accepte les importations au niveau racine et au niveau du groupe :

  • Importations au niveau racine : spécifiées au niveau supérieur du fichier TEST_MAPPING (en dehors de tout groupe de tests). Une importation au niveau racine importe l'ensemble du fichier TEST_MAPPING cible (et ses répertoires parents) comme s'il était écrit dans le chemin d'accès dans lequel il est importé. Toutes les définitions de groupe de tests du fichier importé (par exemple, presubmit, postsubmit) sont fusionnées dans les groupes de tests correspondants du fichier d'importation.
  • Importations au niveau du groupe : spécifiées directement dans un groupe de tests spécifique (tel que presubmit ou postsubmit). Une importation au niveau du groupe est strictement limitée à ce groupe de tests, ce qui signifie que seuls les tests définis sous ce groupe spécifique dans le fichier TEST_MAPPING cible seront importés. Tous les autres groupes de tests du fichier cible sont ignorés.

Les importations au niveau du groupe offrent un contrôle précis sur l'exécution des tests. Par exemple, vous pouvez importer des tests spécifiquement pour l'envoi avant sans extraire de tests lourds après l'envoi à partir du répertoire importé.

Exemple d'importation au niveau racine :

{
  "imports": [
    {
      "path": "frameworks/base/services/core"
    }
  ],
  "presubmit": [
    {
      "name": "MyTestModule"
    }
  ]
}

Exemple d'importation au niveau du groupe :

{
  "presubmit": [
    {
      "name": "MyTestModule"
    },
    {
      "imports": [
        {
          "path": "frameworks/base/services/core"
        }
      ]
    }
  ],
  "postsubmit": [
    {
      "imports": [
        {
          "path": "frameworks/base/services/accessibility"
        }
      ]
    }
  ]
}

L'attribut options contient des options de ligne de commande Tradefed supplémentaires.

Pour obtenir la liste complète des options disponibles pour un test donné, exécutez la commande suivante :

tradefed.sh run commandAndExit [test_module] --help

Pour en savoir plus sur le fonctionnement des options, consultez Gestion des options dans Tradefed.

Validations TEST_MAPPING

Lorsque vous envoyez des modifications qui modifient des fichiers TEST_MAPPING, des vérifications avant l'envoi sont effectuées pour garantir l'exactitude :

  • Validation des modèles de fichiers : s'assure que toutes les expressions régulières de file_patterns correspondent à au moins un fichier du dépôt. Les modèles obsolètes qui ne correspondent à aucun fichier entraînent l'échec de la vérification.
  • Validation au moment de la compilation : promeut les avertissements de validation au moment de la compilation que la compilation collecte pour bloquer les erreurs avant l'envoi pour les branches principales. Voici quelques avertissements courants :
    • Modules inexistants : référence à un nom de module de test qui n'existe pas dans la base de code, par exemple lorsque le nom comporte une faute de frappe.
    • Importations non valides : chemins d'accès de référence dans imports qui n'existent pas ou ne contiennent pas de fichier TEST_MAPPING.
    • Problèmes de schéma : utilisation de clés non compatibles ou de structures mal formées dans la configuration JSON.

Exécuter des tests avec Atest

Pour exécuter les règles de test avant l'envoi en local :

  1. Accédez au répertoire contenant le fichier TEST_MAPPING.
  2. Exécutez la commande suivante :

    atest
    

Tous les tests avant l'envoi configurés dans les fichiers TEST_MAPPING du répertoire actuel et de ses répertoires parents sont exécutés. Atest localise et exécute deux tests avant l'envoi (A et B).

Il s'agit du moyen le plus simple d'exécuter des tests avant l'envoi dans les fichiers TEST_MAPPING du répertoire de travail actuel et des répertoires parents. Atest localise et utilise le fichier TEST_MAPPING dans le répertoire de travail actuel et tous ses répertoires parents.

Structurer le code source

Cet exemple montre comment configurer des fichiers TEST_MAPPING dans l'arborescence source :

src
├── project_1
│   └── TEST_MAPPING
├── project_2
│   └── TEST_MAPPING
└── TEST_MAPPING

Contenu de src/TEST_MAPPING :

{
  "presubmit": [
    {
      "name": "A"
    }
  ]
}

Contenu de src/project_1/TEST_MAPPING :

{
  "presubmit": [
    {
      "name": "B"
    }
  ],
  "postsubmit": [
    {
      "name": "C"
    }
  ],
  "other_group": [
    {
      "name": "X"
    }
  ]}

Contenu de src/project_2/TEST_MAPPING :

{
  "presubmit": [
    {
      "name": "D"
    }
  ],
  "import": [
    {
      "path": "src/project_1"
    }
  ]}

Spécifier les répertoires cibles

Vous pouvez spécifier un répertoire cible pour exécuter des tests dans les fichiers TEST_MAPPING de ce répertoire. La commande suivante exécute deux tests (A, B) :

atest --test-mapping src/project_1

Exécuter des règles de test après l'envoi

Vous pouvez également utiliser cette commande pour exécuter les règles de test après l'envoi définies dans TEST_MAPPING dans src_path (par défaut, le répertoire de travail actuel) et ses répertoires parents :

atest [--test-mapping] [src_path]:postsubmit

N'exécuter que les tests qui ne nécessitent aucun appareil

Vous pouvez utiliser l'option --host pour qu'Atest n'exécute que les tests configurés sur l'hôte qui ne nécessitent aucun appareil. Sans cette option, Atest exécute les deux tests, ceux qui nécessitent un appareil et ceux qui s'exécutent sur un hôte qui ne nécessite aucun appareil. Les tests sont exécutés dans deux suites distinctes :

atest [--test-mapping] --host

Identifier les groupes de tests

Vous pouvez spécifier des groupes de tests dans la commande Atest. La commande suivante exécute tous les tests postsubmit associés aux fichiers du répertoire src/project_1, qui ne contient qu'un seul test (C).

Vous pouvez également utiliser :all pour exécuter tous les tests, quel que soit le groupe. La commande suivante exécute quatre tests (A, B, C, X) :

atest --test-mapping src/project_1:all

Inclure les sous-répertoires

Par défaut, l'exécution de tests dans TEST_MAPPING avec Atest n'exécute que les tests avant l'envoi configurés dans le fichier TEST_MAPPING du répertoire de travail actuel (ou du répertoire donné) et de ses répertoires parents. Si vous souhaitez exécuter des tests dans tous les fichiers TEST_MAPPING des sous-répertoires, utilisez l'option --include-subdir pour forcer Atest à inclure également ces tests.

atest --include-subdir

Sans l'option --include-subdir, Atest n'exécute que le test A. Avec l'option --include-subdir, Atest exécute deux tests (A, B).

Commentaire au niveau de la ligne accepté

Vous pouvez ajouter un commentaire au format // au niveau de la ligne pour compléter le fichier TEST_MAPPING avec une description du paramètre qui suit. ATest et Trade Federation prétraitent TEST_MAPPING au format JSON valide sans commentaires. Pour que le fichier JSON reste propre, seul le commentaire au format // au niveau de la ligne est accepté.

Exemple :

{
  // For presubmit test group.
  "presubmit": [
    {
      // Run test on module A.
      "name": "A"
    }
  ]
}