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, c'est-à-dire le package android , car cela conduirait à 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 lui-même.

Cela signifie qu'un test d'instrumentation ne peut pas s'injecter dans le framework Android, alias le serveur système, pour exécution. Afin de tester le framework Android, le code de test ne peut invoquer que des surfaces d'API publiques, ou celles exposées via Android Interface Definition Language AIDL disponible dans l'arborescence source de la plate-forme. Pour cette catégorie de tests, il n'est pas significatif de cibler un package particulier. Par conséquent, il est courant que de telles instrumentations soient déclarées pour cibler son propre package d'application de test, comme défini dans sa propre <manifest> de AndroidManifest.xml .

Selon les exigences, les packages d'application de test de cette catégorie peuvent également :

  • Regroupez les activités nécessaires pour les tests.
  • Partagez l'ID utilisateur avec le système.
  • Être signé avec la clé de la plateforme.
  • Être compilé sur la source du framework plutôt que sur 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 le source de la plateforme :

L'exemple couvert ici est l'écriture d'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 pour servir d'exemple :

Il est recommandé de parcourir d'abord le code pour avoir une impression approximative avant de continuer.

Décider d'un emplacement source

En règle générale, votre équipe aura déjà un modèle établi d'emplacements pour vérifier 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 ont un sous-répertoire dédié qui contient le code source du composant.

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 des 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 avec du contenu.

Dans certains cas, votre équipe peut avoir d'autres structures de répertoires en cours de 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 à ce qui se trouve dans le répertoire instrumentation dans l'exemple de changement de gerrit. Les sections ci-dessous expliquent plus en détail chaque fichier.

Fichier manifeste

Tout comme une application standard, chaque module de test d'instrumentation a besoin d'un fichier manifeste. Si vous nommez le fichier AndroidManifest.xml et que vous le fournissez à côté de Android.mk pour votre module de test, il sera automatiquement inclus par le makefile de base BUILD_PACKAGE .

Avant d'aller plus loin, il est fortement recommandé de parcourir d'abord la vue d' ensemble du manifeste d'application .

Cela donne un aperçu des composants de base d'un fichier manifeste et de leurs fonctionnalités. Voir l'exemple sur platform_testing/tests/example/instrumentation/AndroidManifest.xml .

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

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

    <application/>

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

</manifest>

Certaines 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 : il s'agit de l'identifiant unique utilisé par le framework d'application Android pour identifier une application (ou dans ce contexte : votre application de test). Chaque utilisateur du système ne peut installer qu'une seule application avec ce nom de package.

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

Veuillez également noter que bien que le nom du package soit généralement dans le même style qu'un nom de package Java, il a en fait 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 votre nom de 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, cet apk doit recevoir le même identifiant d'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 la section ci-dessus), 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é de l'utilisateur system de l'appelant, ce qui nécessite que le package appelant partage l'identifiant de l'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 empaquetées dans un fichier de bibliothèque jar de framework séparé, nécessite donc des entrées de chemin de classe supplémentaires lorsque le package de test est appelé par le framework d'application.

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

Vous avez peut-être remarqué que le targetPackage ici est déclaré identique à l'attribut package déclaré dans la balise manifest de ce fichier. Comme mentionné dans testing basics , cette catégorie de test d'instrumentation est généralement destinée à tester les API de framework, il n'est donc pas très significatif pour eux 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 cibles disponibles pour les développeurs dans Trade Federation et ceux-ci peuvent être utilisés pour s'assurer que l'appareil est correctement configuré avant l'exécution du test.

<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>

Cela 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 de test runner qui est JUnit dans ce cas.

Pour plus d'informations, consultez Tester les configurations du module .

Fonctionnalités de JUnit4

L'utilisation de la bibliothèque android-support-test comme exécuteur de test permet l'adoption de nouvelles classes de test de style JUnit4, et l'exemple de modification de 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 des 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 tenus d'hériter d'une classe de test de base commune ; à la place, vous écrivez des tests dans des classes Java simples et utilisez des annotations pour indiquer certaines configurations et contraintes de test. 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 par JUnit4 pour effectuer la configuration avant le test et le démontage après le 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 dans une classe de test, et le démontage par la suite. Notez que les méthodes de configuration et de démontage de la portée de 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 , à la place, chacune d'entre 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.

Important : les méthodes de test elles-mêmes sont annotées avec l'annotation @Test ; et notez que pour que les tests soient exécutés via APCT, ils doivent être annotés avec des tailles de test : l'exemple annoté la méthode testHelloWorld comme @SmallTest . L'annotation peut être appliquée à la portée de la méthode ou à la portée de la classe.

Accéder aux instrumentation

Bien que cela ne soit pas couvert dans l'exemple de base de hello world, il est assez courant qu'un test Android nécessite l'accès à l'instance Instrumentation : il s'agit de l'interface API principale qui permet d'accéder 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 une classe de base commune, il n'est plus nécessaire d'obtenir l'instance d' Instrumentation via InstrumentationTestCase#getInstrumentation() , à la place, le nouveau testeur 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 lourde, suivez les instructions d'instrumentation .