Conceptos básicos de Espresso

En este documento, se explica cómo completar tareas comunes de pruebas automatizadas con el API de Espresso.

La API de Espresso alienta a los autores de pruebas a pensar en términos de cómo podría un usuario hacer mientras interactúa con la aplicación: ubicar elementos de la IU e interactuar con ellos. Al mismo tiempo, el framework impide el acceso directo a las actividades y vistas de la aplicación, ya que conservar estos objetos y operar desde el subproceso de IU es una gran fuente de inconsistencias en las pruebas. Por lo tanto, no verás métodos como getView() y getCurrentActivity() en la API de Espresso. Puedes seguir operando de forma segura en vistas implementando tus propias subclases de ViewAction y ViewAssertion.

Componentes de la API

Estos son algunos de los componentes principales de Espresso:

  • Espresso: Punto de entrada a las interacciones con las vistas (mediante onView() y onData()). También expone las APIs que no están necesariamente vinculadas a ninguna vista, como como pressBack().
  • ViewMatchers: Es una colección de objetos que implementan el Matcher<? super View>. Puedes pasar uno o más de estos a la onView() para ubicar una vista dentro de la jerarquía de vistas actual.
  • ViewActions: Es una colección de objetos ViewAction a los que se puede pasar. el método ViewInteraction.perform(), como click()
  • ViewAssertions: Es una colección de objetos ViewAssertion que se pueden pasó el método ViewInteraction.check(). La mayoría de las veces, usarás coincide con la aserción, que usa un comparador de vistas para confirmar el estado del vista seleccionada actualmente.

Ejemplo:

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()));

Cómo encontrar una vista

En la gran mayoría de los casos, el método onView() toma un comparador de Hamcrest. que se espera que coincida con una sola vista en la vista actual en la nube. Los comparadores son eficaces y serán familiares para quienes utilizaron con Mockito o JUnit. Si no está familiarizado con los comparadores de Hamcrest, para comenzar con una mirada rápida a esto presentación.

A menudo, la vista deseada tiene un R.id único, y un comparador withId simple reducir la búsqueda de vistas. Sin embargo, hay muchos casos legítimos en los que no se puede determinar R.id en el momento del desarrollo de la prueba. Por ejemplo, la vista específica Es posible que no tenga un R.id o que el R.id no sea único. Esto puede hacer que las pruebas de instrumentación son frágiles y complicadas de escribir porque la forma normal de Acceder a la vista con findViewById() no funciona. Por lo tanto, podrías necesitan acceder a miembros privados de la actividad o el fragmento que conservan la vista o busca un contenedor con un R.id conocido y navega a su contenido para el en una vista en particular.

Espresso se encarga de este problema correctamente, ya que te permite acotar la vista. con objetos ViewMatcher existentes o tus propios objetos personalizados.

Encontrar una vista por su R.id es tan simple como llamar a onView():

Kotlin

onView(withId(R.id.my_view))

Java

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

A veces, los valores de R.id se comparten entre varias vistas. Cuando esto sucede, se Si intentas usar un objeto R.id específico, obtendrás una excepción, como AmbiguousViewMatcherException El mensaje de excepción te proporciona un texto de la jerarquía de vistas actual, que puedes buscar y encontrar las vistas que coinciden con R.id no único:

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

Si revisas los distintos atributos de las vistas, es posible que encuentres propiedades identificables. En el ejemplo anterior, una de las vistas tiene el texto "Hello!" Puedes usar esta opción para acotar tu búsqueda usando la combinación comparadores:

Kotlin

onView(allOf(withId(R.id.my_view), withText("Hello!")))

Java

onView(allOf(withId(R.id.my_view), withText("Hello!")));

También puedes optar por no revertir ninguno de los comparadores:

Kotlin

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

Java

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

Consulta ViewMatchers para los comparadores de vistas que proporciona Espresso.

Consideraciones

  • En una aplicación que se comporta correctamente, todas las vistas con las que un usuario puede interactuar Debe incluir texto descriptivo o incluir una descripción del contenido. Consulta Cómo mejorar la accesibilidad de las apps más detalles. Si no puedes acotar una búsqueda con withText() o withContentDescription(), considera tratarlo como un error de accesibilidad.
  • Usa el comparador menos descriptivo que encuentre la vista que estás buscando. . No especifiques en exceso, ya que esto forzará al framework a hacer más trabajo que es necesaria. Por ejemplo, si una vista se identifica de forma exclusiva por su texto, no es necesario especificar que la vista también se puede asignar desde TextView. Para muchos de los vistas, el R.id de la vista debería ser suficiente.
  • Si la vista de destino está dentro de un AdapterView, como ListView, GridView o Spinner: Es posible que el método onView() no funcione. En estas debes usar onData() en su lugar.

Cómo ejecutar una acción en una vista

Cuando hayas encontrado un comparador adecuado para la vista de destino, realizar instancias de ViewAction en ella con el método de rendimiento

Por ejemplo, para hacer clic en la vista:

Kotlin

onView(...).perform(click())

Java

onView(...).perform(click());

Puedes ejecutar más de una acción con una llamada:

Kotlin

onView(...).perform(typeText("Hello"), click())

Java

onView(...).perform(typeText("Hello"), click());

Si la vista con la que trabajas se encuentra dentro de un ScrollView (vertical o horizontal), considera preceder las acciones que requieren que la vista se se muestra, como click() y typeText(), con scrollTo(). Esta garantiza que la vista se muestre antes de continuar con la otra acción:

Kotlin

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

Java

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

Consulta ViewActions para las acciones de vista proporcionadas por Espresso.

Cómo verificar las aserciones de vistas

Las aserciones se pueden aplicar a la vista seleccionada con el método check(). . La aserción más utilizada es matches(), Utiliza un ViewMatcher para confirmar el estado de la vista seleccionada.

Por ejemplo, para verificar que una vista tenga el texto "Hello!", puede usarse el código siguiente:

Kotlin

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

Java

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

Si deseas confirmar que "Hello!" sea el contenido de la vista, no es recomendable que uses el código siguiente:

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()));

Por otro lado, si deseas afirmar que una vista con el texto "Hello!" es visible (por ejemplo, después de un cambio en la marca de visibilidad de vistas), el código está bien.

Cómo visualizar una prueba de aserción simple

En este ejemplo, SimpleActivity contiene un Button y una TextView. Cuando el botón, el contenido de TextView cambia a "Hello Espresso!".

A continuación, se explica cómo probar el procedimiento anterior con Espresso:

Haz clic en el botón

El primer paso es buscar una propiedad que ayude a encontrar el botón. El del botón en SimpleActivity tiene un R.id único, como se esperaba.

Kotlin

onView(withId(R.id.button_simple))

Java

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

Para hacer clic:

Kotlin

onView(withId(R.id.button_simple)).perform(click())

Java

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

Cómo verificar el texto de TextView

La TextView con el texto para verificar también tiene un R.id único:

Kotlin

onView(withId(R.id.text_simple))

Java

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

Para verificar el texto del contenido:

Kotlin

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))

Java

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));

Cómo verificar la carga de datos en las vistas de adaptador

AdapterView es un tipo especial de widget que carga los datos de forma dinámica desde un adaptador. El ejemplo más común de una AdapterView es ListView. Como a diferencia de los widgets estáticos, como LinearLayout, solo un subconjunto de la Se pueden cargar AdapterView elementos secundarios en la jerarquía de vistas actual. Un sencillo La búsqueda de onView() no encontró las vistas que no están cargadas.

Para controlar esto, Espresso proporciona un punto de entrada onData() independiente, que es cargar por primera vez el elemento adaptador en cuestión y ponerlo en foco antes de que opera en ella o en cualquiera de sus hijos.

Advertencia: Implementaciones personalizadas de Es posible que AdapterView tenga problemas con onData() si no cumplen con los contratos de herencia, en particular API de getItem(). En esos casos, lo mejor es refactorizar el código de la aplicación. Si no puedes hacerlo, puedes implementar un AdapterViewProtocol personalizado que coincide. Para obtener más información, mira la configuración predeterminada Clase AdapterViewProtocols proporcionada por Espresso.

Cómo implementar una prueba simple de la vista de adaptador

En esta prueba simple, se demuestra cómo usar onData(). SimpleActivity contiene un Spinner con algunos elementos que representan tipos de cafés. Cuando un elemento seleccionado, hay un TextView que cambia a "One %s a day!", donde %s representa el elemento seleccionado.

El objetivo de esta prueba es abrir Spinner, seleccionar un elemento específico Verifica que TextView contenga el elemento. Como la clase Spinner se basa en AdapterView, se recomienda usar onData() en lugar de onView() para que coinciden con el elemento.

Cómo abrir la selección del elemento

Kotlin

onView(withId(R.id.spinner_simple)).perform(click())

Java

onView(withId(R.id.spinner_simple)).perform(click());

Cómo seleccionar un elemento

Para la selección del elemento, el Spinner crea una ListView con el contenido correspondiente. Esta vista puede ser muy larga y es posible que el elemento no contribuya a ella. en la nube. Cuando usas onData(), fuerzamos el elemento deseado en la vista. en la nube. Los elementos de Spinner son cadenas, así que queremos hacer coincidir un elemento que sea igual a la cadena "Americano":

Kotlin

onData(allOf(`is`(instanceOf(String::class.java)),
        `is`("Americano"))).perform(click())

Java

onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

Verifica que el texto sea correcto

Kotlin

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))))

Java

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))));

Depuración

Espresso proporciona información útil de depuración cuando falla una prueba:

Registros

Espresso registra todas las acciones de vistas en logcat. Por ejemplo:

ViewInteraction: Performing 'single click' action on view with text: Espresso

Jerarquía de vistas

Espresso imprime la jerarquía de vistas en el mensaje de excepción cuando onView(). falla.

  • Si onView() no encuentra la vista de destino, se genera una NoMatchingViewException arrojado. Puedes examinar la jerarquía de vistas en la cadena de excepción para analizar por qué el comparador no coincidió con ninguna vista.
  • Si onView() encuentra varias vistas que coinciden con el comparador en cuestión, se generará una Se arroja AmbiguousViewMatcherException. Se muestra la jerarquía de vistas las vistas coincidentes se marcan con la etiqueta 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****

Cuando se trata de una jerarquía de vistas complicada o de un comportamiento inesperado de los widgets siempre es útil usar la Hierarchy Viewer en Android Studio para una explicación.

Advertencias sobre la vista del adaptador

Espresso advierte a los usuarios sobre la presencia de widgets de AdapterView. Cuando se produce un onView() la operación arroja un NoMatchingViewException y los widgets de AdapterView presente en la jerarquía de vistas, la solución más común es usar onData(). El mensaje de excepción incluirá una advertencia con una lista de las vistas de adaptador. Puedes usar esta información a fin de invocar a onData() para cargar la vista de destino.

Recursos adicionales

Para obtener más información sobre el uso de Espresso en pruebas de Android, consulta el los siguientes recursos.

Ejemplos