Exemple de tests d'auto-instrumentation

Lorsqu'un test d'instrumentation est démarré, son package cible est redémarré avec le code d'instrumentation injecté et lancé pour exécution. Une exception est que le package cible ici ne peut pas être le framework d'application Android lui-même, comme le package android , car cela conduit à la situation paradoxale où le framework Android devrait être redémarré, ce qui prend en charge les fonctions du système, y compris l'instrumentation elle-même.

Cela signifie qu'un test d'instrumentation ne peut pas s'injecter dans le framework Android, c'est-à-dire le serveur système, pour être exécuté. Afin de tester le framework Android, le code de test peut invoquer uniquement les surfaces d'API publiques ou celles exposées à l'aide du langage de définition d'interface Android AIDL disponible dans l'arborescence des sources de la plateforme. Pour cette catégorie de tests, il n’est pas significatif de cibler un package particulier. Par conséquent, il est habituel que de telles instrumentations soient déclarées pour cibler leur propre package d'application de test, tel que défini dans sa propre balise <manifest> de AndroidManifest.xml .

En fonction des exigences, les packages d'applications de test de cette catégorie peuvent également :

  • Regroupez les activités nécessaires aux tests.
  • Partagez l’ID utilisateur avec le système.
  • Soyez signé avec la clé de la plateforme.
  • Être compilé avec la source du framework plutôt qu'avec le SDK public.

Cette catégorie de tests d'instrumentation est parfois appelée auto-instrumentation. Voici quelques exemples de tests d’auto-instrumentation dans la source de la plateforme :

L'exemple présenté ici consiste à écrire un nouveau test d'instrumentation avec un package cible défini sur son propre package d'application de test. Ce guide utilise le test suivant comme exemple :

Il est recommandé de parcourir d'abord le code pour avoir une idée générale avant de continuer.

Décidez d'un emplacement source

En règle générale, votre équipe disposera déjà d'un modèle établi d'emplacements pour archiver le code et d'emplacements pour ajouter des tests. La plupart des équipes possèdent un seul référentiel git ou en partagent un avec d'autres équipes, mais disposent d'un sous-répertoire dédié contenant le code source des composants.

En supposant que l'emplacement racine de la source de votre composant se trouve à <component source root> , la plupart des composants contiennent des dossiers src et tests , ainsi que certains fichiers supplémentaires tels que Android.mk (ou divisés en fichiers .mk supplémentaires), le fichier manifeste AndroidManifest.xml et le fichier de configuration de test « AndroidTest.xml ».

Puisque vous ajoutez un tout nouveau test, vous devrez probablement créer le répertoire tests à côté de votre composant src et le remplir de contenu.

Dans certains cas, votre équipe peut avoir d'autres structures de répertoires en cours tests en raison de la nécessité de regrouper différentes suites de tests dans des apks individuels. Et dans ce cas, vous devrez créer un nouveau sous-répertoire sous tests .

Quelle que soit la structure, vous finirez par remplir le répertoire tests ou le sous-répertoire nouvellement créé avec des fichiers similaires à ceux du répertoire instrumentation dans l'exemple de changement gerrit. Les détails de chaque fichier sont expliqués plus loin dans ce document.

Fichier manifeste

Comme pour un projet d'application, chaque module de test d'instrumentation nécessite un fichier manifeste appelé AndroidManifest.xml . Pour inclure automatiquement ce fichier à l'aide du makefile principal BUILD_PACKAGE , fournissez ce fichier à côté du fichier Android.mk pour votre module de test.

Si vous n'êtes pas familier avec le fichier AndroidManifest.xml , reportez-vous à la présentation du manifeste de l'application.

Voici un exemple de fichier AndroidManifest.xml :

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  android:sharedUserId="android.uid.system"
  package="android.test.example.helloworld" >

    <application>
       <uses-library android:name="android.test.runner"/>
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.test.example.helloworld"
                     android:label="Hello World Test"/>

</manifest>

Quelques remarques sélectionnées sur le fichier manifeste :

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.test.example.helloworld" >

L'attribut package est le nom du package d'application : c'est l'identifiant unique que le framework d'application Android utilise pour identifier une application (ou dans ce contexte : votre application de test). Chaque utilisateur du système ne peut installer qu'une seule application portant ce nom de package.

De plus, cet attribut package est le même que ce que ComponentName#getPackageName() renvoie, et également le même que vous utiliseriez pour interagir avec diverses sous-commandes pm en utilisant adb shell .

Notez que même si le nom du package est généralement dans le même style qu'un nom de package Java, il a en réalité très peu de choses à voir avec lui. En d'autres termes, votre package d'application (ou de test) peut contenir des classes avec n'importe quel nom de package, mais d'un autre côté, vous pouvez opter pour la simplicité et avoir le nom de votre package Java de niveau supérieur dans votre application ou test identique au nom du package d'application.

android:sharedUserId="android.uid.system"

Cela déclare qu'au moment de l'installation, ce fichier APK doit recevoir le même identifiant utilisateur, c'est-à-dire l'identité d'exécution, que la plate-forme principale. Notez que cela dépend de la signature de l'apk avec le même certificat que la plate-forme principale (voir LOCAL_CERTIFICATE dans une section précédente), mais ce sont des concepts différents :

  • certaines autorisations ou API sont protégées par signature, ce qui nécessite le même certificat de signature
  • certaines autorisations ou API nécessitent l'identité d'utilisateur system de l'appelant, ce qui nécessite que le package appelant partage l'ID utilisateur avec system , s'il s'agit d'un package distinct de la plate-forme principale elle-même
<uses-library android:name="android.test.runner" />

Ceci est requis pour tous les tests d'instrumentation puisque les classes associées sont regroupées dans un fichier de bibliothèque JAR de framework distinct, nécessite donc des entrées de chemin de classe supplémentaires lorsque le package de test est invoqué par le framework d'application.

android:targetPackage="android.test.example.helloworld"

Vous avez peut-être remarqué que le targetPackage ici est déclaré de la même manière que l'attribut package déclaré dans la balise manifest de ce fichier. Comme mentionné dans les principes de base des tests , cette catégorie de tests d'instrumentation est généralement destinée à tester les API du framework, il n'est donc pas très significatif pour elles d'avoir un package d'application ciblé spécifique, autre que lui-même.

Fichier de configuration simple

Chaque nouveau module de test doit avoir un fichier de configuration pour diriger le système de construction avec les métadonnées du module, les dépendances au moment de la compilation et les instructions d'empaquetage. Dans la plupart des cas, l'option de fichier Blueprint basée sur Soong est suffisante. Pour plus de détails, voir Configuration de test simple .

Fichier de configuration complexe

Pour ces cas plus complexes, vous devez également écrire un fichier de configuration de test pour le harnais de test d'Android, Trade Federation .

La configuration de test peut spécifier des options de configuration de périphérique spéciales et des arguments par défaut pour fournir la classe de test. Voir l'exemple sur /platform_testing/tests/example/instrumentation/AndroidTest.xml .

Un instantané est inclus ici pour plus de commodité :

<configuration description="Runs sample instrumentation test.">
  <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
  <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
    <option name="test-file-name" value="HelloWorldTests.apk"/>
  </target_preparer>
  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
  <option name="test-suite-tag" value="apct"/>
  <option name="test-tag" value="SampleInstrumentationTest"/>

  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
    <option name="package" value="android.test.example.helloworld"/>
    <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
  </test>
</configuration>

Quelques remarques sélectives sur le fichier de configuration de test :

<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
  <option name="test-file-name" value="HelloWorldTests.apk"/>
</target_preparer>

Cela indique à Trade Federation d'installer HelloWorldTests.apk sur l'appareil cible à l'aide d'un target_preparer spécifié. Il existe de nombreux préparateurs de cibles à la disposition des développeurs de la Fédération du commerce et ceux-ci peuvent être utilisés pour garantir que l'appareil est correctement configuré avant l'exécution des tests.

<test class="com.android.tradefed.testtype.AndroidJUnitTest">
  <option name="package" value="android.test.example.helloworld"/>
  <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
</test>

Ceci spécifie la classe de test Trade Federation à utiliser pour exécuter le test et passe dans le package sur l'appareil à exécuter et le framework d'exécution de test qui est JUnit dans ce cas.

Pour plus d'informations, consultez Configurations du module de test .

Fonctionnalités JUnit4

L'utilisation de la bibliothèque android-support-test comme exécuteur de tests permet l'adoption de nouvelles classes de test de style JUnit4, et l'exemple de changement gerrit contient une utilisation très basique de ses fonctionnalités. Voir l'exemple sur /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java .

Bien que les modèles de test soient généralement spécifiques aux équipes de composants, il existe certains modèles d'utilisation généralement utiles.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Une différence significative dans JUnit4 est que les tests ne sont plus obligés d'hériter d'une classe de test de base commune ; au lieu de cela, vous écrivez des tests dans des classes Java simples et utilisez des annotations pour indiquer certaines configurations et contraintes de tests. Dans cet exemple, nous indiquons que cette classe doit être exécutée en tant que test JUnit4.

    @BeforeClass
    public static void beforeClass() {
    ...
    @AfterClass
    public static void afterClass() {
    ...
    @Before
    public void before() {
    ...
    @After
    public void after() {
    ...
    @Test
    @SmallTest
    public void testHelloWorld() {
    ...

Les annotations @Before et @After sont utilisées sur les méthodes de JUnit4 pour effectuer la configuration pré-test et le démontage post-test. De même, les annotations @BeforeClass et @AfterClass sont utilisées sur les méthodes par JUnit4 pour effectuer la configuration avant d'exécuter tous les tests d'une classe de test, puis le démontage. Notez que les méthodes de configuration et de démontage de la portée de la classe doivent être statiques. Quant aux méthodes de test, contrairement à la version précédente de JUnit, elles n'ont plus besoin de commencer le nom de la méthode par test , mais chacune d'elles doit être annotée avec @Test . Comme d'habitude, les méthodes de test doivent être publiques, ne déclarer aucune valeur de retour, ne prendre aucun paramètre et peuvent lever des exceptions.

Accès aux cours d'instrumentation

Bien que cela ne soit pas abordé dans l'exemple de base de Hello World, il est assez courant qu'un test Android nécessite un accès à l'instance Instrumentation : il s'agit de l'interface API principale qui donne accès aux contextes d'application, aux API de test liées au cycle de vie des activités, etc.

Étant donné que les tests JUnit4 ne nécessitent plus de classe de base commune, il n'est plus nécessaire d'obtenir l'instance Instrumentation via InstrumentationTestCase#getInstrumentation() , mais le nouveau programme d'exécution de tests la gère via InstrumentationRegistry où la configuration contextuelle et environnementale créée par le framework d'instrumentation est stockée.

Pour accéder à l'instance de la classe Instrumentation , appelez simplement la méthode statique getInstrumentation() sur la classe InstrumentationRegistry :

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Construire et tester localement

Pour les cas d’utilisation les plus courants, utilisez Atest .

Pour les cas plus complexes nécessitant une personnalisation plus importante, suivez les instructions d'instrumentation .