Probar las interacciones de usuario dentro de una sola app ayuda a garantizar que los usuarios no tengan resultados inesperados ni una mala experiencia cuando interactúen con la app. Deberías adquirir el hábito de crear pruebas de interfaz de usuario (IU) si necesitas verificar que la IU de la app funcione correctamente.
El framework de Espresso, entregado por AndroidX Test, proporciona API para escribir pruebas de IU que simulan las interacciones del usuario en una sola app de destino. Las pruebas de Espresso se pueden ejecutar en dispositivos con Android 2.3.3 (API nivel 10) o superior. Una ventaja clave de usar Espresso es que proporciona la sincronización automática de las acciones de prueba con la IU de la app en cuestión.
Espresso detecta cuando el subproceso principal está inactivo, de modo que puede ejecutar los comandos de prueba en el momento adecuado y mejorar la confiabilidad de las pruebas. Con esta capacidad, no se necesita agregar ninguna solución de tiempo, como Thread.sleep()
, en el código de prueba.
El framework de prueba de Espresso es una API basada en instrumentación y funciona con el panel de prueba AndroidJUnitRunner
.
Cómo configurar Espresso
Antes de compilar la prueba de la IU con Espresso, asegúrate de establecer una referencia de dependencia a la biblioteca de Espresso:
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' }
Desactiva las animaciones en el dispositivo de prueba. Si dejas activadas las animaciones del sistema en el dispositivo de prueba, es posible que se produzcan resultados inesperados o que la prueba falle. Para desactivar las animaciones, accede a Configuración, abre Opciones para desarrolladores y desactiva todas las siguientes opciones:
- Escala de animación de ventana
- Escala de animación de transición
- Escala de duración de animador
Si quieres configurar el proyecto para usar funciones de Espresso distintas que las de la API principal, consulta las guías específicas de Espresso.
Cómo crear una clase de prueba de Espresso
Para crear una prueba de Espresso, sigue este modelo de programación:
- Llama a los métodos
onView()
oonData()
de los controlesAdapterView
para buscar el componente de IU que quieres probar en unaActivity
(por ejemplo, un botón de acceso en la app). -
Llama a los métodos
ViewInteraction.perform()
oDataInteraction.perform()
, y pasa la acción del usuario (por ejemplo, un clic en el botón de acceso) para simular una interacción específica del usuario por realizar con el componente de la IU. Para secuenciar varias acciones en el mismo componente de la IU, encadénalas con una lista separada por comas en el argumento del método. - Repite los pasos anteriores según sea necesario, con el fin de simular el flujo de un usuario a través de varias actividades en la app de destino.
- Usa los métodos
ViewAssertions
para comprobar que la IU refleje el estado o el comportamiento esperado después de que se realicen esas interacciones de usuario.
Estos pasos se explican con más detalles en las siguientes secciones.
En el siguiente fragmento de código, se muestra cómo la clase de prueba puede invocar este flujo de trabajo básico:
Kotlin
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher .perform(click()) // click() is a ViewAction .check(matches(isDisplayed())) // matches(isDisplayed()) is a ViewAssertion
Java
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher .perform(click()) // click() is a ViewAction .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
Cómo usar Espresso con ActivityTestRule
En esta sección, se describe cómo crear una prueba de Espresso nueva con el estilo JUnit 4 y usar ActivityTestRule
para reducir la cantidad de código estándar que debes escribir. Con ActivityTestRule
, el framework de prueba inicia la actividad en cuestión antes de cada método de prueba anotado con @Test
y antes de cualquier método anotado con @Before
. El framework controla el cierre de la actividad después de que finaliza la prueba y se ejecutan todos los métodos con la anotación @After
.
Kotlin
package com.example.android.testing.espresso.BasicSample import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import androidx.test.rule.ActivityTestRule import androidx.test.runner.AndroidJUnit4 @RunWith(AndroidJUnit4::class) @LargeTest class ChangeTextBehaviorTest { private lateinit var stringToBetyped: String @get:Rule var activityRule: ActivityTestRule<MainActivity> = ActivityTestRule(MainActivity::class.java) @Before fun initValidString() { // Specify a valid string. stringToBetyped = "Espresso" } @Test fun changeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(stringToBetyped), closeSoftKeyboard()) onView(withId(R.id.changeTextBt)).perform(click()) // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(stringToBetyped))) } }
Java
package com.example.android.testing.espresso.BasicSample; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; @RunWith(AndroidJUnit4.class) @LargeTest public class ChangeTextBehaviorTest { private String stringToBetyped; @Rule public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class); @Before public void initValidString() { // Specify a valid string. stringToBetyped = "Espresso"; } @Test public void changeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(stringToBetyped), closeSoftKeyboard()); onView(withId(R.id.changeTextBt)).perform(click()); // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(stringToBetyped))); } }
Cómo acceder a los componentes de la IU
Antes de que Espresso pueda interactuar con la app en evaluación, primero debes especificar el componente o la vista de la IU. Espresso admite el uso de comparadores Hamcrest para especificar las vistas y los adaptadores en la app.
Para encontrar la vista, llama al método onView()
y pasa un comparador de vistas que especifique la vista a la que apuntas. Este método se describe detalladamente en Cómo especificar un comparador de vistas.
El método onView()
muestra un objeto ViewInteraction
que permite que la prueba interactúe con la vista.
Sin embargo, es posible que la llamada al método onView()
no funcione si quieres ubicar una vista en un diseño RecyclerView
.
En este caso, sigue las instrucciones de Cómo localizar una vista en AdapterView.
Nota: El método onView()
no comprueba si es válida la vista especificada. En su lugar, Espresso busca solo en la jerarquía de vistas actual con el comparador proporcionado.
Si no se encuentra ninguna coincidencia, el método arroja una NoMatchingViewException
.
En el siguiente fragmento de código, se muestra cómo escribir una prueba que accede a un campo EditText
, ingresa una string de texto, cierra el teclado virtual y luego hace clic en un botón.
Kotlin
fun testChangeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()) onView(withId(R.id.changeTextButton)).perform(click()) // Check that the text was changed. ... }
Java
public void testChangeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()); onView(withId(R.id.changeTextButton)).perform(click()); // Check that the text was changed. ... }
Especifica un comparador de vistas
Puedes especificar un comparador de vistas con estos enfoques:
- Llamar a métodos de la clase
ViewMatchers
. Por ejemplo, para encontrar una vista mediante la búsqueda de una string de texto que se muestra, puedes llamar a un método como el siguiente:Kotlin
onView(withText("Sign-in"))
Java
onView(withText("Sign-in"));
También puedes llamar a
withId()
y proporcionar el ID de recurso (R.id
) de la vista, como se muestra en el siguiente ejemplo:Kotlin
onView(withId(R.id.button_signin))
Java
onView(withId(R.id.button_signin));
No se garantiza que los ID de recursos de Android sean únicos. Si la prueba intenta coincidir con un ID de recurso usado por más de una vista, Espresso arrojará un
AmbiguousViewMatcherException
. -
Usar la clase
Matchers
de Hamcrest. Puedes utilizar los métodosallOf()
para combinar varios comparadores, comocontainsString()
yinstanceOf()
. Este enfoque permite filtrar aún más los resultados de las coincidencias, como se muestra en el siguiente ejemplo:Kotlin
onView(allOf(withId(R.id.button_signin), withText("Sign-in")))
Java
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
Puedes usar la palabra clave
not
para filtrar las vistas que no correspondan al comparador, como se muestra en el siguiente ejemplo:Kotlin
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))))
Java
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
Para usar estos métodos en la prueba, importa el paquete
org.hamcrest.Matchers
. Para obtener más información sobre las coincidencias de Hamcrest, consulta el sitio de Hamcrest.
A fin de mejorar el rendimiento de las pruebas de Espresso, especifica la información de coincidencia mínima necesaria para encontrar la vista de destino. Por ejemplo, si se puede identificar una vista de forma única por el texto descriptivo, no es necesario especificar que también se puede asignar desde la instancia TextView
.
Cómo localizar una vista en AdapterView
En un widget AdapterView
, la vista se propaga de forma dinámica con vistas secundarias durante el tiempo de ejecución. Si la vista de destino que quieres probar está dentro de un AdapterView
(como ListView
, GridView
o Spinner
), el método onView()
podría no funcionar porque solo se puede cargar un subconjunto de las vistas en la jerarquía de vistas actual.
Por el contrario, llama al método onData()
para obtener un objeto DataInteraction
a fin de acceder al elemento de vista de destino.
Espresso controla la carga del elemento de vista de destino en la jerarquía de vistas actual y el desplazamiento hasta el elemento de destino y el enfoque del elemento.
Nota: El método onData()
no comprueba si el elemento especificado coincide con una vista. Espresso busca solo la jerarquía de vistas actual. Si no se encuentra ninguna coincidencia, el método arroja una NoMatchingViewException
.
En el siguiente fragmento de código, se muestra cómo usar el método onData()
junto con la coincidencia de Hamcrest para buscar una fila específica en una lista que contiene una string determinada.
En este ejemplo, la clase LongListActivity
contiene una lista de strings expuestas a través de un SimpleAdapter
.
Kotlin
onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo(LongListActivity.ROW_TEXT), `is`("test input"))))
Java
onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input"))));
Cómo realizar acciones
Llama a los métodos ViewInteraction.perform()
o DataInteraction.perform()
para simular las interacciones del usuario con el componente de IU. Debes pasar uno o más objetos ViewAction
como argumentos. Espresso activa cada acción en secuencia según el orden específico y las ejecuta en el subproceso principal.
La clase ViewActions
proporciona una lista de métodos de ayuda para especificar acciones comunes.
Puedes usar esos métodos como accesos directos prácticos en lugar de crear y configurar objetos individuales de ViewAction
. Puedes especificar acciones como las siguientes:
ViewActions.click()
: hace clic en la vista.ViewActions.typeText()
: hace clic en una vista e ingresa una string especificada.-
ViewActions.scrollTo()
: se desplaza hasta la vista. La vista de destino debe ser una subclase deScrollView
, y el valor de la propiedadandroid:visibility
debe serVISIBLE
. En las vistas que extiendenAdapterView
(por ejemplo,ListView
), el métodoonData()
se encarga del desplazamiento. ViewActions.pressKey()
: hace una pulsación de tecla con un código clave especificado.ViewActions.clearText()
: borra el texto en la vista de destino.
Si la vista de destino está dentro de una ScrollView
, primero realiza la acción ViewActions.scrollTo()
para mostrar la vista en la pantalla antes de continuar con otras acciones. La acción ViewActions.scrollTo()
no tendrá ningún efecto si ya se muestra la vista.
Cómo probar las actividades en aislamiento con Espresso Intents
Espresso Intents permite la validación y el stubbing de los intents enviados por una app. Con Espresso Intents, puedes interceptar los intents salientes, hacer un stub del resultado y a enviarlo de vuelta al componente en cuestión para probar una app, una actividad o un servicio de forma aislada.
Para comenzar las pruebas con Espresso Intents, debes agregar la siguiente línea al archivo build.gradle de la app:
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0' }
Para probar un intent, debes crear una instancia de la clase IntentsTestRule, que es muy similar a la clase ActivityTestRule. La clase IntentsTestRule inicializa Espresso Intents antes de cada prueba, termina la actividad del host y libera Espresso Intents después de cada prueba.
La clase de prueba que se muestra en el siguiente fragmento de código proporciona una prueba simple para un intent explícito, y prueba las actividades y los intents creados en el tutorial Cómo compilar tu primera app.
Kotlin
private const val MESSAGE = "This is a test" private const val PACKAGE_NAME = "com.example.myfirstapp" @RunWith(AndroidJUnit4::class) class SimpleIntentTest { /* Instantiate an IntentsTestRule object. */ @get:Rule var intentsRule: IntentsTestRule<MainActivity> = IntentsTestRule(MainActivity::class.java) @Test fun verifyMessageSentToMessageActivity() { // Types a message into a EditText element. onView(withId(R.id.edit_message)) .perform(typeText(MESSAGE), closeSoftKeyboard()) // Clicks a button to send the message to another // activity through an explicit intent. onView(withId(R.id.send_message)).perform(click()) // Verifies that the DisplayMessageActivity received an intent // with the correct package name and message. intended(allOf( hasComponent(hasShortClassName(".DisplayMessageActivity")), toPackage(PACKAGE_NAME), hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE))) } }
Java
@Large @RunWith(AndroidJUnit4.class) public class SimpleIntentTest { private static final String MESSAGE = "This is a test"; private static final String PACKAGE_NAME = "com.example.myfirstapp"; /* Instantiate an IntentsTestRule object. */ @Rule public IntentsTestRule<MainActivity> intentsRule = new IntentsTestRule<>(MainActivity.class); @Test public void verifyMessageSentToMessageActivity() { // Types a message into a EditText element. onView(withId(R.id.edit_message)) .perform(typeText(MESSAGE), closeSoftKeyboard()); // Clicks a button to send the message to another // activity through an explicit intent. onView(withId(R.id.send_message)).perform(click()); // Verifies that the DisplayMessageActivity received an intent // with the correct package name and message. intended(allOf( hasComponent(hasShortClassName(".DisplayMessageActivity")), toPackage(PACKAGE_NAME), hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE))); } }
Para obtener más información acerca de Espresso Intents, consulta la documentación de Espresso Intents disponible en el sitio de AndroidX Test. También puedes descargar las muestras de código IntentsBasicSample y IntentsAdvancedSample.
Cómo hacer pruebas WebViews con Espresso Web
Espresso Web permite probar los componentes WebView
contenidos en una actividad. Usa la API de WebDriver para inspeccionar y controlar el comportamiento de una WebView
.
Para realizar pruebas con Espresso Web, debes agregar la siguiente línea al archivo build.gradle de la app:
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0' }
Cuando creas una prueba con Espresso Web, debes habilitar JavaScript en WebView
al momento de creación de una instancia del objeto ActivityTestRule para probar la actividad. En las pruebas, puedes seleccionar elementos HTML que se muestran en WebView
y simular las interacciones del usuario, como ingresar texto en un cuadro de texto y luego hacer clic en un botón. Una vez completadas las acciones, puedes verificar si los resultados de la página web coinciden con los resultados esperados.
En el siguiente fragmento de código, la clase prueba un componente WebView
con el valor de ID "webview" en la actividad que se está probando.
La prueba typeTextInInput_clickButton_SubmitsForm()
selecciona un elemento <input>
en la página web, ingresa texto y verifica el texto que aparece en otro elemento.
Kotlin
private const val MACCHIATO = "Macchiato" private const val DOPPIO = "Doppio" @LargeTest @RunWith(AndroidJUnit4::class) class WebViewActivityTest { @get:Rule val activityRule = object : ActivityTestRule<WebViewActivity>( WebViewActivity::class.java, false, /* Initial touch mode */ false /* launch activity */ ) { override fun afterActivityLaunched() { // Enable JavaScript. onWebView().forceJavascriptEnabled() } } @Test fun typeTextInInput_clickButton_SubmitsForm() { // Lazily launch the Activity with a custom start Intent per test activityRule.launchActivity(withWebFormIntent()) // Selects the WebView in your layout. // If you have multiple WebViews you can also use a // matcher to select a given WebView, onWebView(withId(R.id.web_view)). onWebView() // Find the input element by ID .withElement(findElement(Locator.ID, "text_input")) // Clear previous input .perform(clearElement()) // Enter text into the input element .perform(DriverAtoms.webKeys(MACCHIATO)) // Find the submit button .withElement(findElement(Locator.ID, "submitBtn")) // Simulate a click via JavaScript .perform(webClick()) // Find the response element by ID .withElement(findElement(Locator.ID, "response")) // Verify that the response page contains the entered text .check(webMatches(getText(), containsString(MACCHIATO))) } }
Java
@LargeTest @RunWith(AndroidJUnit4.class) public class WebViewActivityTest { private static final String MACCHIATO = "Macchiato"; private static final String DOPPIO = "Doppio"; @Rule public ActivityTestRule<WebViewActivity> activityRule = new ActivityTestRule<WebViewActivity>(WebViewActivity.class, false /* Initial touch mode */, false /* launch activity */) { @Override protected void afterActivityLaunched() { // Enable JavaScript. onWebView().forceJavascriptEnabled(); } } @Test public void typeTextInInput_clickButton_SubmitsForm() { // Lazily launch the Activity with a custom start Intent per test activityRule.launchActivity(withWebFormIntent()); // Selects the WebView in your layout. // If you have multiple WebViews you can also use a // matcher to select a given WebView, onWebView(withId(R.id.web_view)). onWebView() // Find the input element by ID .withElement(findElement(Locator.ID, "text_input")) // Clear previous input .perform(clearElement()) // Enter text into the input element .perform(DriverAtoms.webKeys(MACCHIATO)) // Find the submit button .withElement(findElement(Locator.ID, "submitBtn")) // Simulate a click via JavaScript .perform(webClick()) // Find the response element by ID .withElement(findElement(Locator.ID, "response")) // Verify that the response page contains the entered text .check(webMatches(getText(), containsString(MACCHIATO))); } }
Para obtener más información sobre Espresso Web, consulta la documentación de Espresso Web en el sitio de AndroidX Test. También puedes descargar este fragmento de código como parte de la muestra de código de Espresso Web.
Cómo verificar los resultados
Llama a los métodos ViewInteraction.check()
o DataInteraction.check()
para afirmar que la vista de la IU coincide con algún estado esperado. Debes pasar un objeto ViewAssertion
como argumento. Si falla la afirmación, Espresso arroja un AssertionFailedError
.
La clase ViewAssertions
proporciona una lista de métodos de ayuda para especificar afirmaciones comunes. Estas son algunas de las afirmaciones que puedes usar:
doesNotExist
: afirma que ninguna vista coincide con los criterios especificados en la jerarquía de vistas actual.matches
: afirma que la vista especificada existe en la jerarquía de vistas actual y que el estado coincide con un comparador de Hamcrest determinado.selectedDescendentsMatch
: afirma que existen las vistas secundarias especificadas para una vista superior y que el estado coincide con algún comparador de Hamcrest determinado.
En el siguiente fragmento de código, se muestra cómo comprobar que el texto que se muestra en la IU tenga el mismo valor que el texto ingresado previamente en el campo EditText
.
Kotlin
fun testChangeText_sameActivity() { // Type text and then press the button. ... // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(STRING_TO_BE_TYPED))) }
Java
public void testChangeText_sameActivity() { // Type text and then press the button. ... // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(STRING_TO_BE_TYPED))); }
Cómo ejecutar pruebas de Espresso en un dispositivo o emulador
Puedes ejecutar pruebas de Espresso desde Android Studio o desde la línea de comandos. Asegúrate de especificar AndroidJUnitRunner
como el comparador de instrumentación predeterminado en el proyecto.
A fin de ejecutar la prueba de Espresso, sigue los pasos de ejecución de las pruebas de instrumentación que se describen en Cómo comenzar con las pruebas.
También debes leer la Referencia de la API de Espresso.
Recursos adicionales
Para obtener más información sobre el uso de UI Automator en las pruebas de Android, consulta los siguientes recursos.
Ejemplos
- En Muestras de código de Espresso, hay una selección completa de muestras de Espresso.