Principes de base d'Espresso

Ce document explique comment effectuer des tâches de test automatisées courantes à l'aide du API Espresso.

L'API Espresso encourage les auteurs de tests à réfléchir en termes de ce qu'un utilisateur pourrait lors de l'interaction avec l'application : localiser les éléments de l'interface utilisateur et interagir avec eux. Dans le même temps, le framework empêche l'accès direct aux activités et des vues de l'application, car conserver ces objets en dehors du thread UI est une source importante de fragilité des tests. Vous allez donc ne voient pas de méthodes telles que getView() et getCurrentActivity() dans l'API Espresso. Vous pouvez toujours opérer en toute sécurité sur les 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() et onData()). Il expose également les API qui ne sont pas nécessairement liées à une vue, comme en tant que pressBack().
  • ViewMatchers : ensemble d'objets qui implémente l'élément Matcher<? super View>. Vous pouvez transmettre un ou plusieurs de ces éléments onView() pour localiser une vue dans la hiérarchie actuelle des vues.
  • ViewActions : collection d'objets ViewAction pouvant être transmis à la méthode ViewInteraction.perform(), telle que click().
  • ViewAssertions : collection d'objets ViewAssertion pouvant être transmis la méthode ViewInteraction.check(). La plupart du temps, vous utiliserez correspond à une assertion, qui utilise un outil de mise en correspondance des vues pour revendiquer l'état 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. qui doit correspondre à une (et une seule) vue dans la vue actuelle la hiérarchie. Les outils de mise en correspondance sont puissants, que ceux qui ont déjà utilisé avec Mockito ou JUnit. Si vous ne connaissez pas les outils de mise en correspondance des hamcrests, nous vous suggérons de commencer par un aperçu rapide présentation.

Souvent, la vue souhaitée possède un R.id unique et un simple outil de mise en correspondance withId affiner la recherche par vue. Cependant, il existe de nombreux cas légitimes impossible de 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 R.id n'est pas unique. Cela peut avoir un impact les tests d'instrumentation sont fragiles et compliqués à écrire, car la méthode normale l'accès à la vue, avec findViewById(), ne fonctionne pas. Vous pouvez donc besoin d'accéder aux membres privés de l'activité ou du fragment contenant la vue ou rechercher un conteneur avec un R.id connu et accéder à son contenu pour la un point de vue particulier.

Espresso gère parfaitement ce problème 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 sa 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. Dans ce cas, tentative d'utilisation d'un R.id spécifique vous donne une exception, par exemple : AmbiguousViewMatcherException Le message d'exception contient un texte de la hiérarchie des vues actuelle, que vous pouvez rechercher les vues qui correspondent à 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 parcourant les différents attributs des vues, vous trouverez peut-être des propriétés identifiables. Dans l'exemple ci-dessus, l'une des vues comporte le texte "Hello!" Vous pouvez l'utiliser pour affiner votre recherche en combinant outils de mise en correspondance:

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 aucune 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"))));

Voir ViewMatchers pour les outils de mise en correspondance des vues fournis par Espresso.

Points à prendre en compte

  • Dans une application performante, toutes les vues avec lesquelles un utilisateur peut interagir doit contenir un texte descriptif ou une description du contenu. Voir Rendre les applications plus accessibles pour plus plus de détails. Si vous ne parvenez pas à affiner une recherche avec withText() ou withContentDescription(), considérez-le comme un bug d'accessibilité.
  • Utilisez l'outil de mise en correspondance le moins descriptif pour trouver la vue que vous recherchez . Ne spécifiez pas trop de détails, car cela forcera le framework à faire plus de travail que est nécessaire. Par exemple, si une vue est identifiable de manière unique grâce à son texte, vous il n'est pas nécessaire de spécifier que la vue est également attribuable à partir de TextView. Pour de nombreuses vues, le R.id de la vue devrait être suffisant.
  • Si la vue cible se trouve à l'intérieur d'un AdapterView, tel que ListView, GridView ou Spinner : la méthode onView() risque de ne pas fonctionner. Dans ces utilisez plutôt onData().

Effectuer une action sur une vue

Une fois que vous avez trouvé une mise en correspondance adaptée à la vue cible, vous pouvez exécuter des instances de ViewAction sur celui-ci à 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 avec laquelle vous travaillez se trouve dans un ScrollView (vertical ou (horizontale), envisagez les actions précédentes qui nécessitent que la vue soit (click() et typeText(), par exemple) par scrollTo(). Ce garantit que la vue est affichée avant de procéder à l'autre action:

Kotlin

onView(...).perform(scrollTo(), click())

Java

onView(...).perform(scrollTo(), click());

Voir ViewActions pour les actions d'affichage fournies par Espresso.

Vérifier les assertions de la vue

Vous pouvez appliquer des assertions à la vue actuellement sélectionnée à l'aide de l'check() . L'assertion la plus utilisée est matches(). Elle utilise un ViewMatcher pour valider l'état de la vue actuellement sélectionnée.

Par exemple, pour vérifier qu'une vue contient le texte "Hello!":

Kotlin

onView(...).check(matches(withText("Hello!")))

Java

onView(...).check(matches(withText("Hello!")));

Si vous souhaitez déclarer que "Hello!" est le contenu de la vue, les pratiques suivantes sont considérées comme déconseillées:

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 voulez déclarer qu'une vue avec le texte "Hello!" est visible (par exemple, après la modification de l'indicateur de visibilité des vues), le code ne pose pas de problème.

Test simple d'assertion de vue

Dans cet exemple, SimpleActivity contient un Button et un TextView. Lorsque appuie 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. La le bouton dans SimpleActivity a un R.id unique, comme prévu.

Kotlin

onView(withId(R.id.button_simple))

Java

onView(withId(R.id.button_simple));

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, vérifions 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 de l'adaptateur

AdapterView est un type particulier de widget qui charge ses données de manière dynamique depuis un adaptateur. L'exemple le plus courant de AdapterView est ListView. En tant que contrairement aux widgets statiques comme LinearLayout, seul un sous-ensemble AdapterView enfants peuvent être chargés dans la hiérarchie des vues actuelle. Une requête La recherche onView() n'a trouvé aucune vue qui n'est actuellement pas chargée.

Espresso gère cela en fournissant un point d'entrée onData() distinct, qui est de pouvoir charger l'adaptateur en question, en le mettant au premier plan l'utiliser ou ses enfants.

Avertissement:Implémentations personnalisées de AdapterView peut rencontrer des problèmes avec onData(). s'ils rompent les contrats d'héritage, en particulier les API getItem(). Dans ce cas, la meilleure chose à faire est de refactoriser le code de votre application. Si cela n'est pas possible, vous pouvez mettre en œuvre AdapterViewProtocol personnalisé correspondant. Pour en savoir plus, consultez regardez le modèle par défaut <ph type="x-smartling-placeholder"></ph> AdapterViewProtocols fournie par Espresso.

Test simple de la vue de l'adaptateur

Ce test simple montre comment utiliser onData(). SimpleActivity contient un Spinner avec quelques éléments représentant des types de boissons au café. Lorsqu'un est sélectionné, une 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, puis vérifiez 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 correspondant à l'article.

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 d'éléments, Spinner crée un ListView avec son contenu. Cette vue peut être très longue, et l'élément risque de ne pas y contribuer. la hiérarchie. En utilisant onData(), nous forçons l'élément souhaité dans la vue. la hiérarchie. Les éléments dans 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érifier 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 consigne toutes les actions d'affichage dans logcat. 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 lorsque onView(). est défaillant.

  • Si onView() ne trouve pas la vue cible, une NoMatchingViewException est généré. Vous pouvez examiner la hiérarchie des vues dans la chaîne d'exception à analyser pourquoi la mise en correspondance n'a généré aucune correspondance.
  • Si onView() trouve plusieurs vues qui correspondent à l'outil de mise en correspondance donné, une L'exception AmbiguousViewMatcherException est générée. La hiérarchie des vues est imprimée Les vues avec correspondance sont signalées par le 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 compliquée ou de comportement inattendu des widgets il est toujours utile d'utiliser Hierarchy Viewer d'Android Studio pour une explication.

Avertissements concernant la vue Adapter

Espresso avertit les utilisateurs de la présence de widgets AdapterView. Lorsqu'un onView() génère une exception NoMatchingViewException, et les widgets AdapterView sont dans la hiérarchie des vues, la solution la plus courante consiste à utiliser onData(). Le message d'exception comprendra un avertissement avec une 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 lors des tests sur Android, consultez le les ressources suivantes.

Exemples