Ce document explique comment effectuer des tâches de test automatisées courantes à l'aide de l'API Espresso.
L'API Espresso encourage les auteurs de tests à réfléchir à ce qu'un utilisateur pourrait faire lorsqu'il interagit avec l'application : localiser les éléments d'interface utilisateur et interagir avec eux. Dans le même temps, le framework empêche l'accès direct aux activités et aux vues de l'application, car le fait de conserver ces objets et de les utiliser en dehors du thread UI constitue une source majeure de faiblesse de test. Ainsi, vous ne verrez pas de méthodes telles que getView()
et getCurrentActivity()
dans l'API Espresso.
Vous pouvez toujours opérer en toute sécurité sur des vues en implémentant vos propres sous-classes de ViewAction
et ViewAssertion
.
Composants de l'API
Les principaux composants d'Espresso sont les suivants:
- Espresso : point d'entrée des interactions avec les vues (via
onView()
etonData()
). Expose également des API qui ne sont pas nécessairement liées à une vue, commepressBack()
. - ViewMatchers : collection d'objets qui implémentent l'interface
Matcher<? super View>
. Vous pouvez transmettre un ou plusieurs de ces éléments à la méthodeonView()
pour localiser une vue dans la hiérarchie des vues actuelle. - ViewActions : collection d'objets
ViewAction
pouvant être transmis à la méthodeViewInteraction.perform()
, par exempleclick()
. - ViewAssertions : collection d'objets
ViewAssertion
pouvant être transmis à l'aide de la méthodeViewInteraction.check()
. La plupart du temps, vous utiliserez l'assertion "matches", qui utilise un outil de mise en correspondance des vues pour valider l'état de la vue actuellement sélectionnée.
Exemple :
Kotlin
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
Java
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
Rechercher une vue
Dans la grande majorité des cas, la méthode onView()
utilise un outil de mise en correspondance hamcrest censé correspondre à une (et à une seule) vue dans la hiérarchie des vues actuelle. Les outils de mise en correspondance sont puissants et connus de ceux qui les ont utilisés avec Mockito ou JUnit. Si vous n'êtes pas familier avec les outils de mise en correspondance hamcrest, nous vous suggérons de commencer par un rapide coup d'œil à cette présentation.
Souvent, la vue souhaitée possède un R.id
unique et un simple outil de mise en correspondance withId
permet de réduire la recherche de vues. Toutefois, il existe de nombreux cas légitimes où vous ne pouvez pas déterminer R.id
au moment du développement du test. Par exemple, la vue spécifique peut ne pas avoir de R.id
ou le R.id
n'est pas unique. Cela peut rendre les tests d'instrumentation normaux fragiles et compliqués à écrire, car la méthode normale d'accès à la vue (avec findViewById()
) ne fonctionne pas. Ainsi, vous devrez peut-être accéder à des membres privés de l'activité ou du fragment qui contiennent la vue, ou rechercher un conteneur avec un R.id
connu et accéder à son contenu pour la vue particulière.
Espresso gère ce problème correctement en vous permettant d'affiner la vue à l'aide d'objets ViewMatcher
existants ou de vos propres objets personnalisés.
Pour rechercher une vue à l'aide de son R.id
, il suffit d'appeler onView()
:
Kotlin
onView(withId(R.id.my_view))
Java
onView(withId(R.id.my_view));
Parfois, les valeurs R.id
sont partagées entre plusieurs vues. Lorsque cela se produit, une tentative d'utilisation d'un R.id
particulier génère une exception, telle que AmbiguousViewMatcherException
. Le message d'exception fournit une représentation textuelle de la hiérarchie des vues actuelle, dans laquelle vous pouvez rechercher et trouver les vues correspondant à l'élément R.id
non unique:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
En examinant les différents attributs des vues, vous pouvez trouver des propriétés identifiables de manière unique. Dans l'exemple ci-dessus, l'une des vues contient le texte "Hello!"
. Vous pouvez l'utiliser pour affiner votre recherche à l'aide de combinaisons de correspondances:
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Java
onView(allOf(withId(R.id.my_view), withText("Hello!")));
Vous pouvez également choisir de n'inverser aucun des outils de mise en correspondance:
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Java
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
Consultez ViewMatchers
pour obtenir les outils de mise en correspondance des vues fournis par Espresso.
Points à prendre en compte
- Dans une application bien conçue, toutes les vues avec lesquelles un utilisateur peut interagir doivent contenir un texte descriptif ou une description du contenu. Pour en savoir plus, consultez la page Rendre les applications plus accessibles. Si vous ne parvenez pas à affiner une recherche à l'aide de
withText()
ou dewithContentDescription()
, envisagez de la considérer comme un bug d'accessibilité. - Utilisez l'outil de mise en correspondance le moins descriptif qui trouve la vue que vous recherchez. Ne spécifiez pas trop d'informations, car cela obligerait le framework à effectuer plus de travail que nécessaire. Par exemple, si une vue est identifiable de manière unique par son texte, vous n'avez pas besoin de spécifier qu'elle peut également être attribuée à partir de
TextView
. Dans de nombreuses vues, l'élémentR.id
de la vue doit être suffisant. - Si la vue cible se trouve dans un élément
AdapterView
tel queListView
,GridView
ouSpinner
, la méthodeonView()
risque de ne pas fonctionner. Dans ce cas, utilisez plutôtonData()
.
Effectuer une action sur une vue
Lorsque vous avez trouvé un outil de mise en correspondance adapté à la vue cible, il est possible d'y exécuter des instances de ViewAction
à l'aide de la méthode perform.
Par exemple, pour cliquer sur la vue:
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
Vous pouvez exécuter plusieurs actions avec un seul appel "Perform" :
Kotlin
onView(...).perform(typeText("Hello"), click())
Java
onView(...).perform(typeText("Hello"), click());
Si la vue que vous utilisez se trouve dans un ScrollView
(vertical ou horizontal), envisagez les actions précédentes qui nécessitent l'affichage de la vue, telles que click()
et typeText()
, avec scrollTo()
. Cela permet de s'assurer que la vue est affichée avant de passer à l'autre action:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Consultez ViewActions
pour connaître les actions d'affichage fournies par Espresso.
Vérifier les assertions de vue
Les assertions peuvent être appliquées à la vue actuellement sélectionnée à l'aide de la méthode check()
. L'assertion la plus utilisée est matches()
. Elle utilise un objet ViewMatcher
pour revendiquer l'état de la vue actuellement sélectionnée.
Par exemple, pour vérifier qu'une vue comporte le texte "Hello!"
:
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
Si vous souhaitez affirmer que "Hello!"
est le contenu de la vue, ce qui suit est considéré comme une mauvaise pratique:
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Java
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
En revanche, si vous souhaitez affirmer qu'une vue avec le texte "Hello!"
est visible (par exemple, après une modification de l'indicateur de visibilité des vues), le code est parfait.
Afficher le test simple d'assertion
Dans cet exemple, SimpleActivity
contient une Button
et une TextView
. Lorsque l'utilisateur clique sur le bouton, le contenu de TextView
devient "Hello Espresso!"
.
Voici comment tester cela avec Espresso:
Cliquez sur le bouton
La première étape consiste à rechercher une propriété permettant de trouver le bouton. Comme prévu, le bouton dans SimpleActivity
possède un R.id
unique.
Kotlin
onView(withId(R.id.button_simple))
Java
onView(withId(R.id.button_simple));
Maintenant, pour effectuer le clic:
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
Vérifier le texte TextView
Le TextView
avec le texte à vérifier possède également un R.id
unique:
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
Maintenant, pour vérifier le texte du contenu:
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Java
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
Vérifier le chargement des données dans les vues d'adaptateur
AdapterView
est un type spécial de widget qui charge ses données de manière dynamique à partir d'un adaptateur. L'exemple le plus courant de AdapterView
est ListView
. Contrairement aux widgets statiques tels que LinearLayout
, seul un sous-ensemble des enfants AdapterView
peut être chargé dans la hiérarchie des vues actuelle. Une simple recherche onView()
ne permet pas de trouver les vues qui ne sont pas actuellement chargées.
Espresso gère cela en fournissant un point d'entrée onData()
distinct qui peut d'abord charger l'élément d'adaptateur en question, le mettant en évidence avant d'effectuer une opération sur lui ou sur l'un de ses enfants.
Avertissement:Les implémentations personnalisées de AdapterView
peuvent rencontrer des problèmes avec la méthode onData()
si elles rompent les contrats d'héritage, en particulier l'API getItem()
. Dans ce cas, le meilleur plan d'action consiste à refactoriser le code de votre application. Si vous ne pouvez pas le faire, vous pouvez implémenter un AdapterViewProtocol
personnalisé correspondant. Pour en savoir plus, consultez la classe
AdapterViewProtocols
par défaut fournie par Espresso.
Test simple de vue de l'adaptateur
Ce test simple montre comment utiliser onData()
. SimpleActivity
contient un Spinner
avec quelques éléments qui représentent des types de boissons à base de café. Lorsqu'un élément est sélectionné, TextView
devient "One %s a day!"
, où %s
représente l'élément sélectionné.
L'objectif de ce test est d'ouvrir Spinner
, de sélectionner un élément spécifique et de vérifier que TextView
contient l'élément. La classe Spinner
étant basée sur AdapterView
, il est recommandé d'utiliser onData()
au lieu de onView()
pour faire correspondre l'élément.
Ouvrir la sélection d'éléments
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
Java
onView(withId(R.id.spinner_simple)).perform(click());
Sélectionner un élément
Pour la sélection de l'élément, Spinner
crée une ListView
avec son contenu.
Cette vue peut être très longue, et l'élément peut ne pas être ajouté à la hiérarchie des vues. En utilisant onData()
, nous forcerons l'élément souhaité dans la hiérarchie des vues. Les éléments de Spinner
sont des chaînes. Nous voulons donc faire correspondre un élément égal à la chaîne "Americano"
:
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Java
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
Vérifiez que le texte est correct
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Java
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
Débogage
Espresso fournit des informations de débogage utiles en cas d'échec d'un test:
Journalisation
Espresso enregistre toutes les actions de vue dans logcat. Par exemple :
ViewInteraction: Performing 'single click' action on view with text: Espresso
Hiérarchie de vues
Espresso imprime la hiérarchie des vues dans le message d'exception en cas d'échec de onView()
.
- Si
onView()
ne trouve pas la vue cible, uneNoMatchingViewException
est générée. Vous pouvez examiner la hiérarchie des vues dans la chaîne d'exception pour analyser la raison pour laquelle l'outil de mise en correspondance ne correspond à aucune vue. - Si
onView()
trouve plusieurs vues qui correspondent à l'outil de mise en correspondance donné, une exceptionAmbiguousViewMatcherException
est générée. La hiérarchie des vues est imprimée et toutes les vues mises en correspondance sont marquées à l'aide du libelléMATCHES
:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
En cas de hiérarchie des vues complexe ou de comportement inattendu de widgets, il est toujours utile d'utiliser Hierarchy Viewer dans Android Studio pour obtenir des explications.
Avertissements concernant les vues de l'adaptateur
Espresso avertit les utilisateurs de la présence de widgets AdapterView
. Lorsqu'une opération onView()
génère une NoMatchingViewException
et que des widgets AdapterView
sont présents dans la hiérarchie des vues, la solution la plus courante consiste à utiliser onData()
.
Le message d'exception inclura un avertissement avec la liste des vues de l'adaptateur.
Vous pouvez utiliser ces informations pour appeler onData()
afin de charger la vue cible.
Ressources supplémentaires
Pour en savoir plus sur l'utilisation d'Espresso dans les tests Android, consultez les ressources suivantes.
Exemples
- CustomMatcherSample : indique comment étendre Espresso pour qu'il corresponde à la propriété d'indice d'un objet
EditText
. - RecyclerViewSample :
RecyclerView
actions pour Espresso. - (plus...)