Exemple de tests d'auto-instrumentation

Lorsqu'un test d'instrumentation est lancé, son package cible est redémarré avec du code d'instrumentation injecté et lancé pour l'exécution. Une exception est que le package cible ici ne peut pas être le framework d'application Android lui-même, tel que le package android, car cela entraînerait une situation paradoxale où le framework Android devrait être redémarré, ce qui est 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 (également appelé serveur système) pour l'exécution. Pour tester le framework Android, le code de test ne peut appeler que des surfaces d'API publiques ou celles exposées à l'aide du langage de définition d'interface Android AIDL disponible 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. Par conséquent, il est courant que ces instrumentations soient déclarées pour cibler leur propre package d'application de test, comme défini dans leur propre balise <manifest> de AndroidManifest.xml.

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

  • Activités de bundle requises pour les tests.
  • Partagez l'ID utilisateur avec le système.
  • être signés avec la clé de la plate-forme ;
  • ê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 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 à titre d'exemple:

Nous vous recommandons de commencer par parcourir le code pour obtenir une impression approximative avant de continuer.

Choisir un emplacement source

En règle générale, votre équipe a déjà établi un schéma de vérification du code et d'ajout de 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 dans <component source root>, la plupart des composants comportent 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".

Étant donné que 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 sous tests, car elle doit empaqueter différentes suites de tests dans des APK individuels. Dans ce cas, vous devez 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 de compilation principal BUILD_PACKAGE, fournissez-le à côté du fichier Android.mk de votre module de test.

Si vous ne connaissez pas bien le fichier AndroidManifest.xml, consultez la présentation du fichier manifeste d'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 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 avec ce nom de package.

De plus, cet attribut package est identique à ce que ComponentName#getPackageName() renvoie, et également à celui que vous utiliseriez pour interagir avec les différentes sous-commandes pm à l'aide de adb shell.

Notez que, bien que le nom du package soit généralement du même style que celui d'un package Java, il n'a en réalité que très peu de choses à voir avec lui. En d'autres termes, le package de votre application (ou de votre test) peut contenir des classes avec n'importe quel nom de package. Toutefois, vous pouvez opter pour la simplicité et faire en sorte que le nom du package Java de niveau supérieur dans votre application ou votre test soit identique au nom du package de l'application.

android:sharedUserId="android.uid.system"

Cela déclare qu'au moment de l'installation, ce fichier APK doit être attribué au même ID utilisateur, c'est-à-dire à l'identité d'exécution, que la plate-forme principale. Notez que cela dépend de l'APK signé avec le même certificat que la plate-forme de base (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 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 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.
<uses-library android:name="android.test.runner" />

Cela est nécessaire pour tous les tests d'instrumentation, car les classes associées sont empaqueté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 l'élément targetPackage est déclaré de la même manière que l'attribut package déclaré dans la balise manifest de ce fichier. Comme indiqué dans les principes de base des tests, cette catégorie de tests d'instrumentation est généralement destinée à tester les API de framework. Il n'est donc pas très pertinent de disposer d'un package d'application ciblé spécifique, autre que lui-même.

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 au moment de la compilation et les instructions de packaging. Dans la plupart des cas, l'option de fichier de plan basée sur Soong est suffisante. Pour en savoir plus, consultez la section Configuration de test simple.

Fichier de configuration complexe

Dans ces cas plus complexes, vous devez également écrire un fichier de configuration de test pour le banc d'essais d'Android, la Trade Federation.

La configuration de test peut spécifier des options de configuration d'appareil spéciales et des arguments par défaut pour fournir la classe de test. Consultez l'exemple à l'adresse /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Pour plus de commodité, un instantané est inclus:

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

Remarques importantes concernant 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 à la fédération commerciale d'installer le fichier 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 de la Trade Federation à utiliser pour exécuter le test et transmet le package sur l'appareil à exécuter et le framework de l'exécuteur de test, qui est JUnit dans ce cas.

Pour en savoir plus, consultez la section Configurations de test du module.

Fonctionnalités de JUnit4

L'utilisation de la bibliothèque android-support-test en tant qu'exécuteur de test permet d'adopter 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 certains modèles d'utilisation généralement utiles.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Une différence importante 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 par JUnit4 sur les méthodes 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 par JUnit4 sur les méthodes pour effectuer la configuration avant d'exécuter tous les tests d'une classe de test, puis pour effectuer le démontage. Notez que les méthodes de configuration et de démontage de portée de classe doivent être statiques. Comme pour les méthodes de test, contrairement à la version précédente de JUnit, il n'est plus nécessaire 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, n'accepter aucun paramètre et pouvoir générer des exceptions.

Accès à la classe d'instrumentation

Bien que cela ne soit pas abordé dans l'exemple Hello World de base, il est assez courant qu'un test Android nécessite une instance d'accès 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 de l'activité, 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(). Le nouvel exécuteur de test 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, il suffit d'appeler 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 importante, suivez les instructions d'instrumentation.