Test des interfaces utilisateur Android avec Espresso

Dans cet article, vous apprendrez à rédiger des tests d'interface utilisateur avec le framework de tests Espresso et à automatiser votre processus de test, au lieu d'utiliser le processus manuel fastidieux et sujet aux erreurs.. 

Espresso est un framework de test permettant d'écrire des tests d'interface utilisateur sous Android. Selon la documentation officielle, vous pouvez:

Utilisez Espresso pour écrire des tests d'interface utilisateur Android concis, esthétiques et fiables..

1. Pourquoi utiliser l'espresso?

L'un des problèmes des tests manuels est que cela peut être long et fastidieux. Par exemple, pour tester (manuellement) un écran de connexion dans une application Android, vous devez procéder comme suit:

  1. Lancer l'application. 
  2. Naviguer vers l'écran de connexion. 
  3. Confirmez si le nom d'utilisateurEditText et passwordEditText sont visibles. 
  4. Tapez le nom d'utilisateur et mot de passe dans leurs champs respectifs. 
  5. Confirmez si le bouton de connexion est également visible, puis cliquez sur ce bouton de connexion..
  6. Vérifiez si les vues correctes sont affichées lorsque cette connexion a réussi ou a échoué. 

Au lieu de passer tout ce temps à tester manuellement notre application, il serait préférable de passer plus de temps à écrire du code qui distingue notre application des autres! Et, même si les tests manuels sont fastidieux et assez lents, ils sont toujours sujets aux erreurs et vous pourriez rater certains cas particuliers.. 

Certains des avantages des tests automatisés sont les suivants:   

  • Les tests automatisés exécutent exactement les mêmes cas de test chaque fois qu'ils sont exécutés. 
  • Les développeurs peuvent rapidement détecter un problème avant qu'il ne soit envoyé à l'équipe d'assurance qualité.. 
  • Cela peut faire gagner beaucoup de temps, contrairement aux tests manuels. En gagnant du temps, les ingénieurs logiciels et l'équipe d'assurance qualité peuvent à la place passer plus de temps à des tâches stimulantes et enrichissantes. 
  • Une couverture de test plus élevée est obtenue, ce qui conduit à une application de meilleure qualité. 

Dans ce didacticiel, nous en apprendrons davantage sur Espresso en l'intégrant dans un projet Android Studio. Nous allons écrire des tests d'interface utilisateur pour un écran de connexion et un RecyclerVoir, et nous allons en apprendre davantage sur les intentions de test. 

La qualité n'est pas un acte, c'est une habitude. - Pablo Picasso

2. Prérequis

Pour pouvoir suivre ce tutoriel, vous aurez besoin de:

  • une compréhension de base des principales API Android et de Kotlin
  • Android Studio 3.1.3 ou supérieur
  • Kotlin plugin 1.2.51 ou supérieur

Vous trouverez un exemple de projet (en Kotlin) pour ce tutoriel sur notre dépôt GitHub afin que vous puissiez suivre facilement.

3. Créer un projet Android Studio

Lancez votre Android Studio 3 et créez un nouveau projet avec une activité vide appelée Activité principale. Assurez-vous de vérifier Inclure le support Kotlin

4. Configurer Espresso et AndroidJUnitRunner

Après avoir créé un nouveau projet, veillez à ajouter les dépendances suivantes à partir de la bibliothèque de support de test Android dans votre build.gradle (bien que Android Studio les ait déjà inclus pour nous). Dans ce tutoriel, nous utilisons la dernière version de la bibliothèque Espresso 3.0.2 (à ce jour). 

android //… defaultConfig //… testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" //… dépendances // ... androidTestImplementation 'com.android.support.test.espresso: espresso-core: 3.0. 2 'androidTestImplementation' com.android.support.test: runner: 1.0.2 'androidTestImplementation' com.android.support.test: règles: 1.0.2 '

Nous avons également inclus le coureur d'instrumentation AndroidJUnitRunner:

Un Instrumentation qui exécute des tests JUnit3 et JUnit4 sur un package Android (application).

Notez que Instrumentation est simplement une classe de base pour l'implémentation du code d'instrumentation d'application. 

Désactiver l'animation 

La synchronisation d'Espresso, qui ne sait pas comment attendre la fin d'une animation, peut entraîner l'échec de certains tests si vous autorisez l'animation sur votre appareil de test. Pour désactiver l'animation sur votre appareil de test, allez à Réglages > Options de développeur et désactivez toutes les options suivantes dans la section "Dessin": 

  • Échelle d'animation de fenêtre
  • Échelle d'animation de transition
  • Échelle de durée de l'animateur

5. Rédigez votre premier test dans un espresso

Premièrement, nous commençons par tester un écran de connexion. Voici comment commence le flux de connexion: l’utilisateur lance l’application et le premier écran présenté contient un seul S'identifier bouton. Quand cela S'identifier le bouton est cliqué, il ouvre la LoginActivité écran. Cet écran ne contient que deux Éditer le textes (les champs nom d'utilisateur et mot de passe) et un Soumettre bouton. 

Voici ce que notre Activité principale la disposition ressemble à:

Voici ce que notre LoginActivité la disposition ressemble à:

Écrivons maintenant un test pour notre Activité principale classe. Aller à votre Activité principale classe, déplacez le curseur sur le Activité principale nom et appuyez sur Maj-Contrôle-T. Sélectionner Créer un nouveau test… dans le menu contextuel. 

appuyez sur la D'accord bouton, et une autre boîte de dialogue apparaît. Choisir la androidTest répertoire et cliquez sur le D'accord bouton une fois de plus. Notez que, puisque nous écrivons un test d’instrumentation (tests spécifiques au SDK Android), les cas de test résident dans androidTest / java dossier. 

Maintenant, Android Studio a créé avec succès une classe de test pour nous. Au-dessus du nom de la classe, incluez cette annotation: @RunWith (AndroidJUnit4 :: class).

importer android.support.test.runner.AndroidJUnit4 importer org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) classe MainActivityTest 

Cette annotation signifie que tous les tests de cette classe sont des tests spécifiques à Android..

Activités de test

Parce que nous voulons tester une activité, nous devons faire une petite configuration. Nous devons indiquer à Espresso quelle activité ouvrir ou lancer avant de l'exécuter et la détruire après l'exécution d'une méthode de test. 

importer android.support.test.rule.ActivityTestRule importer android.support.test.runner.AndroidJUnit4 importer org.junit.Rule importer org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) classe MainActivityTest @R activityRule = ActivityTestRule(MainActivity :: class.java)

Notez que le @Règle L'annotation signifie qu'il s'agit d'une règle de test JUnit4. Les règles de test JUnit4 sont exécutées avant et après chaque méthode de test (annotée avec @Tester). Dans notre propre scénario, nous voulons lancer Activité principale avant chaque méthode de test et le détruire après. 

Nous avons également inclus le @JvmField Annotation en Kotlin. Ceci indique simplement au compilateur de ne pas générer de getters et de setters pour la propriété mais de l'exposer comme un simple champ Java..

Voici les trois étapes principales de la rédaction d’un test Espresso:

  • Recherchez le widget (par exemple. Affichage ou Bouton) tu veux tester.
  • Effectuer une ou plusieurs actions sur ce widget. 
  • Vérifier ou vérifier si ce widget est maintenant dans un certain état. 

Les types d'annotations suivants peuvent être appliqués aux méthodes utilisées dans la classe de test..

  • @Avant les cours: cela indique que la méthode statique à laquelle cette annotation est appliquée doit être exécutée une fois et avant tous les tests de la classe. Cela pourrait être utilisé, par exemple, pour établir une connexion à une base de données. 
  • @Avant: indique que la méthode à laquelle cette annotation est attachée doit être exécutée avant chaque méthode de test de la classe.
  • @Tester: indique que la méthode à laquelle cette annotation est attachée doit être exécutée en tant que scénario de test.
  • @Après: indique que la méthode à laquelle cette annotation est attachée doit être exécutée après chaque méthode de test. 
  • @Après les cours: indique que la méthode à laquelle cette annotation est attachée doit être exécutée une fois que toutes les méthodes de test de la classe ont été exécutées. Ici, nous fermons généralement les ressources qui ont été ouvertes dans @Avant les cours

Trouver un Vue En utilisant En vue()

Dans notre Activité principale fichier de mise en page, nous avons juste un widget-la S'identifier bouton. Testons un scénario où un utilisateur trouvera ce bouton et cliquera dessus..

importer android.support.test.espresso.Espresso.onView importer android.support.test.espresso.matcher.ViewMatchers.withId //… @RunWith (AndroidJUnit4 :: class) classe MainActivityTest //… @Test @Throws (Exception: : class) fun clickLoginButton_opensLoginUi () onView (withId (R.id.btn_login))

Pour trouver des widgets dans Espresso, nous utilisons les En vue() méthode statique (au lieu de findViewById ()). Le type de paramètre que nous fournissons à En vue() est un Matcher. Notez que le Matcher L'API ne provient pas du SDK Android, mais du projet Hamcrest. La bibliothèque matcher de Hamcrest se trouve dans la bibliothèque Espresso que nous avons extraite via Gradle. 

le onView (withId (R.id.btn_login)) retournera un ViewInteraction c'est pour un Vue dont l'identifiant est R.id.btn_login. Dans l'exemple ci-dessus, nous avons utilisé avecId () rechercher un widget avec un identifiant donné. Les autres correspondants d'affichage que nous pouvons utiliser sont: 

  • avecText (): retourne un matcher qui correspond Affichage basé sur sa valeur de propriété de texte.
  • avecHint (): retourne un matcher qui correspond Affichage basé sur sa valeur de propriété d'indice.
  • avecTagKey (): retourne un matcher qui correspond Vue basé sur les clés de balises.
  • withTagValue (): retourne un matcher qui correspond Vues basé sur les valeurs de propriété de la balise.

Commençons par tester pour voir si le bouton est réellement affiché à l'écran. 

onView (withId (R.id.btn_login)). check (correspond à (isDisplayed ()))

Ici, nous ne faisons que confirmer si le bouton avec l'identifiant donné (R.id.btn_login) est visible pour l'utilisateur, nous utilisons donc le vérifier() méthode pour confirmer si le sous-jacent Vue a un certain état dans notre cas, s'il est visible.

le allumettes() méthode statique renvoie un générique ViewAssertion qui affirme qu’une vue existe dans la hiérarchie des vues et qu’elle correspond au matcher de vue donné. Ce point de vue donné est retourné en appelant est affiché(). Comme suggéré par le nom de la méthode, est affiché() est un matcher qui correspond Vues qui sont actuellement affichés à l'écran pour l'utilisateur. Par exemple, si nous voulons vérifier si un bouton est activé, nous passons simplement est autorisé() à allumettes()

Nous pouvons passer d’autres joueurs de match populaires dans le allumettes() méthode sont:

  • hasFocus (): retourne un matcher qui correspond Vues qui ont actuellement le focus.
  • est vérifié(): retourne un matcher qui accepte si et seulement si la vue est un CompoundButton (ou sous-type de) et est en état vérifié. Le contraire de cette méthode est isNotChecked ()
  • est sélectionné(): retourne un matcher qui correspond Vues qui sont sélectionnés.

Pour exécuter le test, vous pouvez cliquer sur le triangle vert en regard de la méthode ou du nom de la classe. En cliquant sur le triangle vert à côté du nom de la classe, toutes les méthodes de test de cette classe seront exécutées, tandis que celle située à côté d'une méthode exécutera le test uniquement pour cette méthode.. 

Hourra! Notre test a réussi!


Effectuer des actions sur une vue

Sur un ViewInteraction objet qui est retourné en appelant En vue(), nous pouvons simuler des actions qu'un utilisateur peut effectuer sur un widget. Par exemple, nous pouvons simuler une action de clic en appelant simplement le Cliquez sur() méthode statique à l'intérieur du ViewActions classe. Cela retournera un ViewAction objet pour nous. 

La documentation dit que ViewAction est:

Responsable d'effectuer une interaction sur l'élément View donné.
@Test fun clickLoginButton_opensLoginUi () //… onView (withId (R.id.btn_login)). Perform (click ())

Nous effectuons un événement clic en appelant d'abord effectuer(). Cette méthode exécute la ou les actions spécifiées sur la vue sélectionnée par l’agresseur de vue en cours. Notez que nous pouvons lui passer une action unique ou une liste d’actions (exécutées dans l’ordre). Ici, nous l'avons donné Cliquez sur(). Les autres actions possibles sont:

  • typeText () imiter la saisie de texte dans un Éditer le texte.
  • effacer le texte() simuler la suppression de texte dans un Éditer le texte.
  • doubleClick () simuler un double-clic sur un Vue.
  • longClick () imiter un long clic sur un Vue.
  • scrollTo () simuler le défilement d'un ScrollView à un particulier Vue c'est visible. 
  • swipeLeft () simuler un balayage de droite à gauche sur le centre vertical d'un Vue.

On peut trouver beaucoup plus de simulations à l’intérieur du ViewActions classe. 

Valider avec les assertions de vue

Terminons notre test, pour valider que le LoginActivité l'écran est affiché chaque fois que le S'identifier bouton est cliqué. Bien que nous ayons déjà vu comment utiliser vérifier() sur un ViewInteraction, Utilisons-le encore, en passant un autre ViewAssertion

@Test fun clickLoginButton_opensLoginUi () //… onView (withId (R.id.tv_login)). Check (correspond à (isDisplayed ()))

À l'intérieur de LoginActivité fichier de mise en page, à part Éditer le textes et un Bouton, nous avons aussi un Affichage avec ID R.id.tv_login. Donc, nous faisons simplement un contrôle pour confirmer que le Affichage est visible pour l'utilisateur. 

Maintenant, vous pouvez relancer le test!

Vos tests devraient réussir si vous avez suivi toutes les étapes correctement. 

Voici ce qui s'est passé pendant le processus d'exécution de nos tests: 

  1. A lancé le Activité principale en utilisant le activityRule champ.
  2. Vérifié si le S'identifier bouton (R.id.btn_login) était visible (est affiché()) à l'utilisateur.
  3. Simulé une action de clic (Cliquez sur()) sur ce bouton.
  4. Vérifié si le LoginActivité a été montré à l'utilisateur en vérifiant si un Affichage avec identifiant R.id.tv_login dans le LoginActivité est visible.

Vous pouvez toujours vous référer à la feuille de triche Espresso pour voir les différents outils de correspondance de vue, les actions de vue et les assertions de vue disponibles.. 

6. Testez le LoginActivité Écran

Voici notre LoginActivity.kt:

importer android.os.Bundle importer android.support.v7.app.AppCompatActivity importer android.widget.Button importer android.widget.EditText importer android.widget.TextView class LoginActivity: AppCompatActivity () (private name loginTitleTextView: TextView private lateinit var passwordEditText: EditText private lateinit var submitButton: Bouton redéfinition fun surCréer (saveInstanceState: Bundle?) super.onCréer (savedInstanceState) setContentView (R.layout.activity_activity_rdin) mettre en formeTherder = findViewById (R.id.et_password) submitButton = findViewById (R.id.btn_submit) loginTitleTextView = findViewById (R.id.tv_login) submitButton.setOnClickListener if (usernameEditText.toS). text.toString () == "mot de passe") loginTitleTextView.text = "Succès" else loginTitleTextView.text = "Échec"

Dans le code ci-dessus, si le nom d'utilisateur entré est "chike" et le mot de passe "mot de passe", la connexion est réussie. Pour toute autre entrée, c'est un échec. Écrivons maintenant un test d'espresso pour cela!

Aller à LoginActivity.kt, déplacez le curseur sur LoginActivité nom et appuyez sur Maj-Contrôle-T. Sélectionner Créer un nouveau test…  dans le menu contextuel. Suivez le même processus que nous avons fait pour MainActivity.kt, et cliquez sur le D'accord bouton. 

importer android.support.test.espresso.Espresso importer android.support.test.espresso.Espresso.onView importer android.support.test.espresso.action.ViewActions importer android.support.test.espresso.assertion.ViewAssertions.matches importer android .support.test.espresso.matcher.ViewMatchers.withId importer android.support.test.espresso.matcher.ViewMatchers.withText importer android.support.test.rule.ActivityTestRule importer etroid.support.test.runner.AndroidJUnit4 import org.jun .Rule import org.junit.Test import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) classe LoginActivityTest @Rule @JvmField var activityRule = ActivityTestRule(LoginActivity :: class.java) private val username = "chike" private val password = "password" @Test fun clickLoginButton_opensLoginUi () ) onView (withId (R.id.et_username)). Effectuer (ViewActions.typeText (nomutilisateur)) onView (withId (R.id.et_password)). perform (ViewActions.typeText (mot de passe)) onView (withId (R.id.btn_submit)). perform (ViewActions.scrollTo (), ViewActions.click ()) Espresso.onView (withId (R.id.tv_login)) .check (correspond à (withText ("Success")))

Cette classe de test est très similaire à la première. Si nous exécutons le test, notre LoginActivité l'écran est ouvert. Le nom d'utilisateur et le mot de passe sont saisis dans le champ R.id.et_username et R.id.et_password champs respectivement. Ensuite, Espresso va cliquer sur le bouton Soumettre bouton (R.id.btn_submit). Il va attendre qu'un Vue avec identifiant R.id.tv_login peut être trouvé avec la lecture du texte Succès

7. Testez un RecyclerVoir

RecyclerViewActions est la classe qui expose un ensemble d’API pour fonctionner sur un RecyclerVoir. RecyclerViewActions fait partie d'un artefact séparé à l'intérieur du espresso-contrib artefact, qui devrait également être ajouté à build.gradle:

androidTestImplementation 'com.android.support.test.espresso: espresso-contrib: 3.0.2' 

Notez que cet artefact contient également l'API pour l'interface utilisateur testant le tiroir de navigation via TiroirActions et DrawerMatchers

@RunWith (AndroidJUnit4 :: class) class MyListActivityTest //… @Test fun clickItem () onView (withId (R.id.rv)) .perform (RecyclerViewActions .actionOnItemAtPosition(0, ViewActions.click ()))

Pour cliquer sur un élément à n'importe quelle position dans un RecyclerVoir, nous invoquons actionOnItemAtPosition (). Nous devons lui donner un type d'article. Dans notre cas, l'article est le ViewHolder classe à l'intérieur de notre RandomAdapter. Cette méthode prend également en deux paramètres; le premier est la position et le second est l'action (ViewActions.click ()). 

Autre RecyclerViewActions qui peuvent être effectuées sont:

  • actionOnHolderItem (): effectue un ViewAction sur une vue correspondant à viewHolderMatcher. Cela nous permet de faire correspondre ce qui est contenu à l'intérieur du ViewHolder plutôt que la position. 
  • scrollToPosition (): retourne un ViewAction qui défile RecyclerVoir à une position.

Ensuite (une fois que "l'écran d'ajout de note" sera ouvert), nous entrerons le texte de notre note et l'enregistrerons. Nous n'avons pas besoin d'attendre que le nouvel écran s'ouvre pour que Espresso le fasse automatiquement pour nous. Il attend qu'une vue avec l'identifiant R.id.add_note_title peut être trouvé.

8. Intentions de test

Espresso utilise un autre artefact nommé intentions expresso pour tester les intentions. Cet artefact est juste une autre extension d'Espresso qui se concentre sur la validation et les moqueries des intentions. Regardons un exemple.

Tout d'abord, nous devons tirer le intentions expresso bibliothèque dans notre projet. 

androidTestImplementation 'com.android.support.test.espresso: espresso-intents: 3.0.2'
importer android.support.test.espresso.intent.rule.IntentsTestRule importer android.support.test.runner.AndroidJUnit4 importer org.junit.Rule importer org.junit.runner.RunWith @RunWith (AndroidJUnit4 (classJapp4)) Rule @JvmField var intentRule = IntentsTestRule(PickContactActivity :: class.java)

IntentionsTestRule s'étend ActivityTestRule, de sorte qu'ils ont tous deux des comportements similaires. Voici ce que dit le doc:

Cette classe est une extension de ActivityTestRule, qui initialise Espresso-Intents avant chaque test annoté avec Tester et libère Espresso-Intents après chaque test. L’activité sera terminée après chaque test et cette règle peut être utilisée de la même manière que ActivityTestRule.

La principale caractéristique de différenciation est qu'il dispose de fonctionnalités supplémentaires pour les tests startActivity () et startActivityForResult () avec des moquettes et des talons. 

Nous allons maintenant tester un scénario dans lequel un utilisateur cliquera sur un bouton (R.id.btn_select_contact) à l'écran pour choisir un contact dans la liste des contacts du téléphone. 

//… @Test fun stubPick () var result = Instrumentation.ActivityResult (Activity.RESULT_OK, Intent (null, ContactsContract.Contacts.CONTENT_URI)) intentionnel (hasAction (Intent.ACTION_PICK)). RespondWith (résultat) onView (withId ( R.id.btn_select_contact)). Perform (click ()) intentionnellement (allOf (toPackage ("com.google.android.contacts"), hasAction (Intent.ACTION_PICK), hasData (ContactsContract.CONTENT_URI))) // …

Ici nous utilisons l'intention () du intentions expresso bibliothèque pour mettre en place un talon avec une réponse fictive pour notre ACTION_PICK demande. Voici ce qui se passe dans  PickContactActivity.kt lorsque l'utilisateur clique sur le bouton avec l'identifiant R.id.btn_select_contact choisir un contact.

fun pickContact (v: View) val i = Intention (Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) startActivityForResult (i, PICK_REQUEST)

l'intention () prend dans un Matcher qui correspond aux intentions pour lesquelles la réponse abrégée doit être fournie. En d'autres termes, le Matcher identifie la demande que vous souhaitez interchanger. Dans notre propre cas, nous utilisons hasAction () (une méthode d'assistance dans IntentMatchers) pour trouver notre ACTION_PICK demande. Nous invoquons alors répondre avec(), qui définit le résultat pour onActivityResult (). Dans notre cas, le résultat a Activity.RESULT_OK, simuler l'utilisateur en sélectionnant un contact dans la liste. 

Nous simulons ensuite en cliquant sur le bouton de sélection du contact, qui appelle startActivityForResult (). Notez que notre fiche a envoyé la réponse fictive à onActivityResult ()

Enfin, nous utilisons le prévu() méthode d'assistance pour valider simplement que les appels à startActivity () et startActivityForResult () ont été faites avec la bonne information. 

Conclusion

Dans ce tutoriel, vous avez appris à utiliser facilement le framework de test Espresso dans votre projet Android Studio pour automatiser votre flux de travail de test.. 

Je recommande fortement de consulter la documentation officielle pour en savoir plus sur la rédaction de tests d'interface utilisateur avec Espresso.