Prueba la IU para una sola app

Cuando pruebas las interacciones de los usuarios en una app ayudas a garantizar que no encontrarán resultados inesperados ni tendrán una mala experiencia. Debes adquirir el hábito de crear pruebas de la interfaz del usuario (IU) de tu app con el fin de verificar que funciona correctamente.

El marco de trabajo de prueba de Espresso, proporcionado por AndroidX Test, proporciona API para escribir pruebas de IU que simulan las interacciones de los usuarios en una única app de destino. Las pruebas de Espresso se pueden ejecutar en dispositivos con Android 2.3.3 (API nivel 10) y versiones posteriores. Una ventaja clave de usar Espresso es que proporciona una sincronización automática de las acciones de prueba con la IU de la app que estás probando. Espresso detecta cuando el subproceso principal está inactivo, de modo que puede ejecutar los comandos de prueba en el momento adecuado, mejorando la confiabilidad de las pruebas. Con esta capacidad, no es necesario agregar ninguna solución, como Thread.sleep(), en el código de prueba.

El marco de trabajo 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 que no sean las que proporciona 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:

  1. Busca el componente de la IU que quieres probar en una Activity (por ejemplo, un botón de acceso en la app) llamando al método onView() o al método onData() para los controles de AdapterView.
  2. Simula una interacción específica del usuario que se ejecutará en ese componente de la IU llamando al método ViewInteraction.perform() o DataInteraction.perform(), y pasando la acción del usuario (por ejemplo, hacer clic en el botón de acceso). Para secuenciar varias acciones en el mismo componente de IU, encadénalas con una lista separada por comas en el argumento del método.
  3. 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.
  4. Usa los métodos ViewAssertions para comprobar que la IU refleja el estado o el comportamiento esperado después de que el usuario lleva a cabo estas interacciones.

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
    

Usa Espresso con ActivityTestRule

En la siguiente sección, se describe cómo crear una nueva prueba de Espresso con el estilo de JUnit 4 y cómo usar ActivityTestRule para reducir la cantidad de código estándar que debes escribir. Cuando usas ActivityTestRule, el marco de trabajo inicia la actividad en modo de prueba antes de cada método de prueba con la anotación @Test y antes de cualquier método con la anotación @Before. El marco de trabajo 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)));
        }
    }
    

Accede a los componentes de la IU

Antes de que Espresso pueda interactuar con la app en modo de prueba, primero debes especificar el componente o la vista de la IU. Espresso admite el uso de los comparadores Hamcrest para especificar las vistas y los adaptadores en tu 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 la vista especificada es válida. En su lugar, Espresso busca solo en la jerarquía de vista actual, utilizando el comparador proporcionado. Si no se encuentra ninguna coincidencia, el método genera una instancia de 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:

  • Llama a métodos de la clase ViewMatchers. Por ejemplo, para buscar una vista mediante 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"));
        

    De manera similar, 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 hacer coincidir un ID de recurso utilizado por más de una vista, Espresso generará una instancia de AmbiguousViewMatcherException.

  • Usa la clase de Hamcrest Matchers. Puedes usar los métodos allOf() para combinar varios comparadores, como containsString() y instanceOf(). 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 corresponden 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.

Para 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 una vista se puede identificar de forma única por su texto descriptivo, no es necesario especificar que también se puede asignar desde la instancia TextView.

Localiza una vista en AdapterView

En un widget AdapterView, la vista se completa de forma dinámica con vistas secundarias en el tiempo de ejecución. Si la vista de destino que quieres probar está dentro de una instancia de AdapterView (como ListView, GridView o Spinner), es posible que el método onView() no funcione porque solo se puede cargar un subconjunto de las vistas en la jerarquía de vista actual.

En su lugar, llama al método onData() para obtener un objeto DataInteraction con el fin de acceder al elemento de la vista de destino. Espresso se encarga de cargar el elemento de vista de destino en la jerarquía de vista actual y también del desplazamiento hasta el elemento de destino y del 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 vista actual. Si no se encuentra ninguna coincidencia, el método genera una instancia de NoMatchingViewException.

En el siguiente fragmento de código, se muestra cómo puedes 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"))));
    

Realiza acciones

Llama a los métodos ViewInteraction.perform() o DataInteraction.perform() para simular las interacciones del usuario en el componente de la 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 estos métodos como accesos directos prácticos en lugar de crear y configurar objetos individuales de ViewAction. Puedes especificar acciones como las siguientes:

Si la vista de destino está dentro de un ScrollView, realiza la acción ViewActions.scrollTo() primero para mostrar la vista en la pantalla antes de continuar con otras acciones. La acción ViewActions.scrollTo() no tendrá ningún efecto si la vista ya se muestra.

Prueba las actividades en aislamiento con Espresso Intents

Espresso Intents permite la validación y el stubbing de los intentos enviados por una app. Con Espresso Intents, puedes probar una app, una actividad o un servicio de forma aislada mediante la interceptación de intents salientes, hacer un stub del resultado y enviarlo de nuevo al componente que se está probando.

Para comenzar las pruebas con Espresso Intents, debes agregar la siguiente línea al archivo build.gradle de tu 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 en el sitio de AndroidX Test. También puedes descargar las muestras de código IntentsBasicSample y IntentsAdvancedSample.

Prueba WebViews con Espresso Web

Espresso Web permite probar los componentes de WebView contenidos en una actividad. Utiliza la API WebDriver para inspeccionar y controlar el comportamiento de una WebView.

Para comenzar las pruebas con Espresso Web, debes agregar la siguiente línea al archivo build.gradle de tu app:

    dependencies {
      androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0'
    }
    

Cuando creas una prueba con Espresso Web, debes habilitar JavaScript en WebView en el 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 comprueba 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.

Verifica los resultados

Llama al método ViewInteraction.check() o DataInteraction.check() para afirmar que la vista en la IU coincide con algún estado esperado. Debes pasar un objeto ViewAssertion como argumento. Si la afirmación falla, Espresso genera una instancia de 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 vista actual.
  • matches: Afirma que la vista especificada existe en la jerarquía de vista actual y que su estado coincide con un comparador Hamcrest determinado.
  • selectedDescendentsMatch: Afirma que existen las vistas secundarias especificadas para una vista principal y que su 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 tiene 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 tu proyecto.

Con el fin de ejecutar la prueba de Espresso, sigue los pasos para ejecutar las pruebas instrumentadas 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

Codelabs