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..
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:
nom d'utilisateurEditText
et passwordEditText
sont visibles. 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:
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
Pour pouvoir suivre ce tutoriel, vous aurez besoin de:
Vous trouverez un exemple de projet (en Kotlin) pour ce tutoriel sur notre dépôt GitHub afin que vous puissiez suivre facilement.
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.
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.
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":
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 texte
s (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..
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:
Affichage
ou Bouton
) tu veux tester.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
. 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 Vue
s 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 Vue
s 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 Vue
s 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 Vue
s 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!
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.
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 texte
s 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:
Activité principale
en utilisant le activityRule
champ.R.id.btn_login
) était visible (est affiché()
) à l'utilisateur.Cliquez sur()
) sur ce bouton.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..
LoginActivité
ÉcranVoici 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.
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é.
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 deActivityTestRule
, qui initialise Espresso-Intents avant chaque test annoté avecTester
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 queActivityTestRule
.
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.
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.