Exemple de tests d'auto-instrumentation

Lorsqu'un test d'instrumentation est lancé, son package cible est redémarré avec le code d'instrumentation injecté et lancé pour l'exécution. Une exception est que le package cible ne peut pas être le framework d'application Android lui-même, tel que le package android, car cela conduit à la situation paradoxale où le framework Android devrait être redémarré, ce qui prend en charge les fonctions 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 s'exécuter. Pour tester le framework Android, le code de test ne peut appeler que les surfaces d'API publiques ou celles exposées à l'aide du langage de définition d'interface Android AIDL disponibles dans l'arborescence source de la plate-forme. Pour cette catégorie de tests, il n'est pas pertinent de cibler un package en particulier. Il est donc d'usage que ces instrumentations soient déclarées pour cibler leur propre package d'application de test, tel que défini dans leur propre tag <manifest> de AndroidManifest.xml.

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

  • Regroupez les activités nécessaires aux tests.
  • Partagez l'ID utilisateur avec le système.
  • être signée avec la clé de la plate-forme ;
  • être compilé par rapport à la source du framework plutôt qu'au 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 plate-forme :

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

Nous vous recommandons de parcourir d'abord le code pour vous faire une idée générale avant de continuer.

Choisir un emplacement source

En général, votre équipe aura déjà établi un modèle de lieux à vérifier dans le code et de lieux où ajouter des tests. La plupart des équipes possèdent un seul dépôt 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 comportent des dossiers src et tests en dessous, 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".

Comme 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épertoire sous tests en raison de la nécessité d'empaqueter différentes suites de tests dans des APK individuels. Dans ce cas, vous devrez créer un 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 semblables à ceux du répertoire instrumentation dans l'exemple de modification 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 fichier makefile principal BUILD_PACKAGE, fournissez ce fichier à côté du fichier Android.mk pour votre module de test.

Si vous ne connaissez pas le fichier AndroidManifest.xml, consultez la présentation du fichier 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>

Voici quelques remarques importantes concernant le fichier manifeste :

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

L'attribut package correspond au nom du package de l'application. Il s'agit de 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 identique à celui renvoyé par ComponentName#getPackageName(), et à celui que vous utiliseriez pour interagir avec diverses sous-commandes pm utilisant adb shell.

Notez que, bien que le nom du package soit généralement du même style qu'un nom de package Java, il n'a en fait que 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. Vous pouvez également opter pour la simplicité et avoir le nom de package Java de premier niveau dans votre application ou test identique au nom de package de l'application.

android:sharedUserId="android.uid.system"

Cela déclare qu'au moment de l'installation, cet fichier APK doit se voir attribuer le même ID 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 il s'agit de concepts différents :

  • Certaines autorisations ou API sont protégées par une signature, ce qui nécessite le même certificat de signature.
  • Certaines autorisations ou API nécessitent l'identité utilisateur system de l'appelant, ce qui exige 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" />

Cela est nécessaire pour tous les tests d'instrumentation, car les classes associées sont regroupées dans un fichier de bibliothèque JAR de framework distinct. Par conséquent, des entrées de chemin de classe supplémentaires sont requises 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 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 utile qu'elles aient un package d'application cible spécifique, autre que le leur.

Fichier de configuration simple

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

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 spéciales de configuration de l'appareil et des arguments par défaut à fournir à la classe de test. Consultez 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>

Voici quelques remarques sur le fichier de configuration du 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é. De nombreux préparateurs de cibles sont disponibles pour les développeurs dans Trade Federation. Ils peuvent être utilisés pour s'assurer 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>

Cela spécifie la classe de test Trade Federation à utiliser pour exécuter le test et transmet le package sur l'appareil à exécuter ainsi que le framework de l'exécuteur de test, qui est JUnit dans ce cas.

Pour en savoir plus, consultez Configurations des modules 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. L'exemple de modification Gerrit contient une utilisation très basique de ses fonctionnalités. Consultez l'exemple à l'adresse /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. Au lieu de cela, 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 la suppression 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 d'une classe de test, et la suppression après. Notez que les méthodes de configuration et de nettoyage de portée de classe doivent être statiques. Quant aux méthodes de test, contrairement aux versions antérieures de JUnit, elles n'ont plus besoin de commencer le nom de la méthode par test. Au lieu de cela, 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 générer des exceptions.

Accès à la classe Instrumentation

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

Étant donné que les tests JUnit4 ne nécessitent plus de classe de base commune, il n'est plus nécessaire d'obtenir une instance Instrumentation via InstrumentationTestCase#getInstrumentation(). Le nouvel exécuteur de test la gère plutôt via InstrumentationRegistry, où est stockée la configuration contextuelle et environnementale créée par le framework d'instrumentation.

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

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Compiler et tester en local

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

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