Ya está disponible la segunda Vista previa para desarrolladores de Android 11; pruébala y comparte tus comentarios.

Prueba la IU para varias apps

Una prueba de la interfaz de usuario (IU) que involucra interacciones del usuario en varias app te permite verificar que tu app se comporta correctamente cuando el flujo del usuario cruza a otras apps o a la IU del sistema. Un ejemplo de ese flujo del usuario es una aplicación de mensajería que permite al usuario ingresar un mensaje de texto, inicia el selector de contactos de Android para que los usuarios puedan seleccionar destinatarios a quienes enviar el mensaje y luego muestra el control a la app original para que el usuario envíe el mensaje.

En esta lección, se abarca cómo escribir tales pruebas de la IU usando el marco de trabajo de prueba de UI Automator provisto por AndroidX Test. Las API de UI Automator permiten interactuar con elementos visibles en un dispositivo, independientemente de qué Activity esté enfocada. La prueba puede buscar un componente de la IU mediante descriptores adecuados, como el texto que se muestra en ese componente o su descripción de contenido. Las pruebas de UI Automator se pueden ejecutar en dispositivos con Android 4.3 (API nivel 18) o versiones posteriores.

El marco de trabajo de prueba de UI Automator es una API basada en instrumentación que funciona con el panel de prueba AndroidJUnitRunner.

También debes leer la referencia de la API de UI Automator y probar las muestras de código de UI Automator.

Cómo configurar UI Automator

Antes de compilar la prueba de la IU con UI Automator, asegúrate de configurar la ubicación del código fuente de prueba y las dependencias del proyecto, como se describe en Cómo configurar el proyecto para AndroidX Test.

En el archivo build.gradle del módulo de la app de Android, debes establecer una referencia de dependencia a la biblioteca de UI Automator:

    dependencies {
        ...
        androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
    }
    

Para optimizar las pruebas de UI Automator, primero debes inspeccionar los componentes de UI de la app de destino y asegurarte de que se pueda acceder a ellos. Estos consejos de optimización se describen en las siguientes dos secciones.

Inspecciona la IU en un dispositivo

Antes de diseñar la prueba, inspecciona los componentes de la IU que están visibles en el dispositivo. Para asegurarte de que las pruebas de UI Automator puedan acceder a estos componentes, comprueba que los componentes tengan etiquetas de texto visibles, valores de android:contentDescription o ambos.

La herramienta uiautomatorviewer proporciona una interfaz visual útil para inspeccionar la jerarquía de diseño y ver las propiedades de los componentes de la IU que están visibles en el primer plano del dispositivo. Esta información te permite crear pruebas más detalladas utilizando UI Automator. Por ejemplo, puedes crear un selector de IU que coincida con una propiedad visible específica.

Para iniciar la herramienta uiautomatorviewer:

  1. Inicia la app de destino en un dispositivo físico.
  2. Conecta el dispositivo a la máquina de desarrollo.
  3. Abre una ventana en el terminal y navega hasta el directorio <android-sdk>/tools/.
  4. Ejecuta la herramienta con este comando:
    $ uiautomatorviewer

Para ver las propiedades de la IU de tu aplicación:

  1. En la interfaz de uiautomatorviewer, haz clic en el botón Device Screenshot del dispositivo.
  2. Desplázate sobre la instantánea en el panel de la izquierda para ver los componentes de la IU identificados por la herramienta uiautomatorviewer. Las propiedades se muestran en el panel inferior derecho y la jerarquía de diseño se muestra en el panel superior derecho.
  3. Opcionalmente, puedes hacer clic en el botón Toggle NAF Nodes para ver los componentes de la IU a los que no puede acceder UI Automator. Es posible que solo haya información limitada disponible para estos componentes.

Para obtener información sobre los tipos comunes de componentes de la IU proporcionados por Android, consulta Interfaz del usuario.

Asegúrate de que se pueda acceder a tu actividad

El marco de trabajo de prueba de UI Automator tiene un mejor rendimiento en las apps que tienen funciones de accesibilidad de Android implementadas. Cuando utilizas elementos de la IU de tipo View, o una subclase de View del SDK, no necesitas implementar compatibilidad de accesibilidad, dado que estas clases ya lo han hecho por ti.

Sin embargo, algunas apps usan elementos de IU personalizados para proporcionar una experiencia del usuario enriquecida. Estos elementos no proporcionarán compatibilidad de accesibilidad automática. Si tu app contiene instancias de una subclase de View que no pertenece al SDK, asegúrate de agregar funciones de accesibilidad a estos elementos completando los siguientes pasos:

  1. Crea una clase concreta que extienda ExploreByTouchHelper.
  2. Asocia una instancia de tu nueva clase con un elemento de IU personalizado específico llamando a setAccessibilityDelegate().

Para obtener una guía sobre cómo agregar funciones de accesibilidad a los elementos de vista personalizada, consulta Cómo compilar vistas personalizadas accesibles. Para obtener más información sobre las prácticas recomendadas generales de accesibilidad en Android, consulta Cómo crear apps más accesibles.

Cómo crear una clase de prueba de UI Automator

Debes escribir la clase de prueba de UI Automator de la misma manera que una clase de prueba de JUnit 4. Para obtener más información sobre la creación de clases de prueba de JUnit 4 y el uso de aserciones y anotaciones de JUnit 4, consulta Cómo crear una clase de prueba de unidad instrumentada.

Agrega la anotación @RunWith(AndroidJUnit4.class) al comienzo de la definición de la clase de prueba. También debes especificar la clase AndroidJUnitRunner, proporcionada en AndroidX Test, como el panel de prueba predeterminado. Este paso se describe con más detalle en Cómo ejecutar pruebas de UI Automator en un dispositivo o emulador.

Implementa el siguiente modelo de programación en la clase de prueba de UI Automator:

  1. Obtén un objeto UiDevice para acceder al dispositivo que quieres probar llamando al método getInstance() y pasando un objeto Instrumentation como argumento.
  2. Obtén un objeto UiObject para acceder a un componente de IU que se muestra en el dispositivo (por ejemplo, la vista actual en primer plano), llamando al método findObject().
  3. Simula una interacción del usuario específica en ese componente de la IU llamando a un método UiObject; por ejemplo, llama a performMultiPointerGesture() para simular un gesto multitáctil y a setText() para editar un campo de texto. Puedes llamar a las API en los pasos 2 y 3 repetidamente según sea necesario, con el fin de probar interacciones de usuario más complejas que involucren varios componentes de la IU o secuencias de acciones del usuario.
  4. Después de que se realicen estas interacciones del usuario, comprueba si la IU refleja el estado o el comportamiento esperados.

Estos pasos se explican con más detalles en las siguientes secciones.

Accede a los componentes de la IU

El objeto UiDevice es la forma principal en la que accedes al el estado del dispositivo y lo manipulas. En las pruebas, puedes llamar a los métodos UiDevice para comprobar el estado de varias propiedades, como la orientación actual o el tamaño de visualización. La prueba puede usar el objeto UiDevice para realizar acciones en el dispositivo, como forzar una rotación específica en el dispositivo, presionar los botones de hardware del pad direccional y presionar los botones Inicio y Menú.

Se recomienda comenzar la prueba desde la pantalla principal del dispositivo. Desde la pantalla principal (o alguna otra ubicación de inicio que hayas elegido en el dispositivo), puedes llamar a los métodos proporcionados por la API de UI Automator para seleccionar elementos específicos de la IU e interactuar con ellos.

En el siguiente fragmento de código, se muestra cómo la prueba puede obtener una instancia de UiDevice y simular que presionas un botón de la página principal:

Kotlin

    import org.junit.Before
    import androidx.test.runner.AndroidJUnit4
    import androidx.test.uiautomator.UiDevice
    import androidx.test.uiautomator.By
    import androidx.test.uiautomator.Until
    ...

    private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"
    private const val LAUNCH_TIMEOUT = 5000L
    private const val STRING_TO_BE_TYPED = "UiAutomator"

    @RunWith(AndroidJUnit4::class)
    @SdkSuppress(minSdkVersion = 18)
    class ChangeTextBehaviorTest2 {

        private lateinit var device: UiDevice

        @Before
        fun startMainActivityFromHomeScreen() {
            // Initialize UiDevice instance
            device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

            // Start from the home screen
            device.pressHome()

            // Wait for launcher
            val launcherPackage: String = device.launcherPackageName
            assertThat(launcherPackage, notNullValue())
            device.wait(
                    Until.hasObject(By.pkg(launcherPackage).depth(0)),
                    LAUNCH_TIMEOUT
            )

            // Launch the app
            val context = ApplicationProvider.getApplicationContext<Context>()
            val intent = context.packageManager.getLaunchIntentForPackage(
                    BASIC_SAMPLE_PACKAGE).apply {
                // Clear out any previous instances
                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
            }
            context.startActivity(intent)

            // Wait for the app to appear
            device.wait(
                    Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
                    LAUNCH_TIMEOUT
            )
        }

    }
    

Java

    import org.junit.Before;
    import androidx.test.runner.AndroidJUnit4;
    import androidx.test.uiautomator.UiDevice;
    import androidx.test.uiautomator.By;
    import androidx.test.uiautomator.Until;
    ...

    @RunWith(AndroidJUnit4.class)
    @SdkSuppress(minSdkVersion = 18)
    public class ChangeTextBehaviorTest {

        private static final String BASIC_SAMPLE_PACKAGE
                = "com.example.android.testing.uiautomator.BasicSample";
        private static final int LAUNCH_TIMEOUT = 5000;
        private static final String STRING_TO_BE_TYPED = "UiAutomator";
        private UiDevice device;

        @Before
        public void startMainActivityFromHomeScreen() {
            // Initialize UiDevice instance
            device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

            // Start from the home screen
            device.pressHome();

            // Wait for launcher
            final String launcherPackage = device.getLauncherPackageName();
            assertThat(launcherPackage, notNullValue());
            device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
                    LAUNCH_TIMEOUT);

            // Launch the app
            Context context = ApplicationProvider.getApplicationContext();
            final Intent intent = context.getPackageManager()
                    .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
            // Clear out any previous instances
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
            context.startActivity(intent);

            // Wait for the app to appear
            device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
                    LAUNCH_TIMEOUT);
        }
    }
    

En el ejemplo, @SdkSuppress(minSdkVersion = 18) ayuda a garantizar que las pruebas solo se ejecutarán en dispositivos con Android 4.3 (API nivel 18) o versiones posteriores, según lo requiera el marco de UI Automator.

Usa el método findObject() para recuperar un UiObject, que representa una vista que coincide con un criterio de selección determinado. Puedes reutilizar las instancias de UiObject que hayas creado en otras partes de las pruebas de tu app, según sea necesario. Ten en cuenta que el marco de trabajo de prueba de UI Automator busca una coincidencia en la pantalla actual cada vez que la prueba usa una instancia de UiObject con el fin de hacer clic en un elemento de la IU o consultar una propiedad.

En el siguiente fragmento, se muestra cómo la prueba puede construir instancias de UiObject que representen un botón Cancelar y un botón Aceptar en una app.

Kotlin

    val cancelButton: UiObject = device.findObject(
            UiSelector().text("Cancel").className("android.widget.Button")
    )
    val okButton: UiObject = device.findObject(
            UiSelector().text("OK").className("android.widget.Button")
    )

    // Simulate a user-click on the OK button, if found.
    if (okButton.exists() && okButton.isEnabled) {
        okButton.click()
    }
    

Java

    UiObject cancelButton = device.findObject(new UiSelector()
            .text("Cancel")
            .className("android.widget.Button"));
    UiObject okButton = device.findObject(new UiSelector()
            .text("OK")
            .className("android.widget.Button"));

    // Simulate a user-click on the OK button, if found.
    if(okButton.exists() && okButton.isEnabled()) {
        okButton.click();
    }
    

Especifica un selector

Si quieres acceder a un componente de la IU específico en una app, usa la clase UiSelector. Esta clase representa una consulta para elementos específicos de la IU que se muestra actualmente.

Si se encuentra más de un elemento coincidente, el primer elemento coincidente en la jerarquía de diseño se muestra como UiObject de destino. Si construyes un UiSelector, puedes encadenar varias propiedades para definir mejor la búsqueda. Si no se encuentra ningún elemento coincidente en la IU, se genera una UiAutomatorObjectNotFoundException.

Puedes usar el método childSelector() para anidar varias instancias de UiSelector. Por ejemplo, en el siguiente ejemplo de código, se muestra cómo la prueba puede especificar una búsqueda para encontrar la primera instancia de ListView en la IU que se muestra actualmente, y luego buscar dentro de esa ListView para encontrar un elemento de IU con la propiedad de texto Apps.

Kotlin

    val appItem: UiObject = device.findObject(
            UiSelector().className("android.widget.ListView")
                    .instance(0)
                    .childSelector(
                            UiSelector().text("Apps")
                    )
    )
    

Java

    UiObject appItem = device.findObject(new UiSelector()
            .className("android.widget.ListView")
            .instance(0)
            .childSelector(new UiSelector()
            .text("Apps")));
    

Cuando especifiques un selector, se recomienda usar un ID de recurso (si hay uno asignado a un elemento de la IU) en lugar de un elemento de texto o un descriptor de contenido. No todos los elementos tienen un elemento de texto (por ejemplo, íconos en una barra de herramientas). Los selectores de texto son frágiles y pueden provocar fallas en las pruebas si hay cambios menores en la IU. También es posible que no escalen a diferentes idiomas; es posible que los selectores de texto no coincidan con las strings traducidas.

Puede ser útil especificar el estado del objeto en los criterios de selección. Por ejemplo, si quieres seleccionar una lista de todos los elementos marcados para poder desmarcarlos, llama al método checked() con el argumento establecido en true.

Realiza acciones

Una vez que la prueba haya obtenido un objeto UiObject, puedes llamar a los métodos en la clase UiObject para realizar interacciones del usuario en el componente de la IU representado por ese objeto. Puedes especificar acciones como las siguientes:

  • click(): Hace clic en el centro de los límites visibles del elemento de la IU.
  • dragTo(): Arrastra este objeto a coordenadas arbitrarias.
  • setText(): Establece el texto en un campo editable, después de borrar el contenido del campo. Por su parte, el método clearTextField() borra el texto existente en un campo editable.
  • swipeUp(): Realiza la acción de deslizar hacia arriba en UiObject. Del mismo modo, los métodos swipeDown(), swipeLeft() y swipeRight() realizan las acciones correspondientes.

El marco de prueba de UI Automator permite enviar un Intent o iniciar una Activity sin usar comandos del shell, obteniendo un objeto Context través de getContext().

En el siguiente fragmento, se muestra cómo la prueba puede usar un Intent para iniciar la aplicación en modo de prueba. Este enfoque es útil si solo te interesa probar la app de la calculadora y no el selector.

Kotlin

    fun setUp() {
        ...

        // Launch a simple calculator app
        val context = getInstrumentation().context
        val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply {
            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
        }
        // Clear out any previous instances
        context.startActivity(intent)
        device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT)
    }
    

Java

    public void setUp() {
        ...

        // Launch a simple calculator app
        Context context = getInstrumentation().getContext();
        Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(CALC_PACKAGE);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

        // Clear out any previous instances
        context.startActivity(intent);
        device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
    }
    

Realiza acciones en colecciones

Usa la clase UiCollection si quieres simular interacciones del usuario en una colección de elementos (por ejemplo, canciones de un álbum de música o una lista de correos electrónicos en una bandeja de entrada). Para crear un objeto UiCollection, especifica un UiSelector que busque un contenedor de IU o un wrapper de otros elementos de la IU secundarios, como una vista de diseño que contenga elementos secundarios de la IU.

En el siguiente fragmento de código, se muestra cómo la prueba puede construir una UiCollection para representar un álbum de video que se muestra dentro de un FrameLayout:

Kotlin

    val videos = UiCollection(UiSelector().className("android.widget.FrameLayout"))

    // Retrieve the number of videos in this collection:
    val count = videos.getChildCount(
            UiSelector().className("android.widget.LinearLayout")
    )

    // Find a specific video and simulate a user-click on it
    val video: UiObject = videos.getChildByText(
            UiSelector().className("android.widget.LinearLayout"),
            "Cute Baby Laughing"
    )
    video.click()

    // Simulate selecting a checkbox that is associated with the video
    val checkBox: UiObject = video.getChild(
            UiSelector().className("android.widget.Checkbox")
    )
    if (!checkBox.isSelected) checkBox.click()
    

Java

    UiCollection videos = new UiCollection(new UiSelector()
            .className("android.widget.FrameLayout"));

    // Retrieve the number of videos in this collection:
    int count = videos.getChildCount(new UiSelector()
            .className("android.widget.LinearLayout"));

    // Find a specific video and simulate a user-click on it
    UiObject video = videos.getChildByText(new UiSelector()
            .className("android.widget.LinearLayout"), "Cute Baby Laughing");
    video.click();

    // Simulate selecting a checkbox that is associated with the video
    UiObject checkBox = video.getChild(new UiSelector()
            .className("android.widget.Checkbox"));
    if(!checkBox.isSelected()) checkbox.click();
    

Realiza acciones en vistas desplazables

Usa la clase UiScrollable para simular el desplazamiento vertical u horizontal en una pantalla. Esta técnica es útil cuando un elemento de la IU está fuera de la pantalla y es necesario un desplazamiento para verlo.

En el siguiente fragmento de código, se muestra cómo simular el desplazamiento hacia abajo en el menú Configuración y cómo simular que se hace clic en la opción "Acerca de" de la tablet:

Kotlin

    val settingsItem = UiScrollable(UiSelector().className("android.widget.ListView"))
    val about: UiObject = settingsItem.getChildByText(
            UiSelector().className("android.widget.LinearLayout"),
            "About tablet"
    )
    about.click()
    

Java

    UiScrollable settingsItem = new UiScrollable(new UiSelector()
            .className("android.widget.ListView"));
    UiObject about = settingsItem.getChildByText(new UiSelector()
            .className("android.widget.LinearLayout"), "About tablet");
    about.click();
    

Verifica los resultados

InstrumentationTestCase extiende TestCase, por lo que puedes utilizar los métodos estándar de JUnit Assert para probar que los componentes de la IU en la app mostrarán los resultados esperados.

En el siguiente fragmento, se muestra cómo la prueba puede ubicar varios botones en una app de calculadora, hacer clic en ellos en orden y luego verificar que se muestra el resultado correcto.

Kotlin

    private const val CALC_PACKAGE = "com.myexample.calc"

    fun testTwoPlusThreeEqualsFive() {
        // Enter an equation: 2 + 3 = ?
        device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("two")).click()
        device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("plus")).click()
        device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("three")).click()
        device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("equals")).click()

        // Verify the result = 5
        val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result"))
        assertEquals("5", result.text)
    }
    

Java

    private static final String CALC_PACKAGE = "com.myexample.calc";

    public void testTwoPlusThreeEqualsFive() {
        // Enter an equation: 2 + 3 = ?
        device.findObject(new UiSelector()
                .packageName(CALC_PACKAGE).resourceId("two")).click();
        device.findObject(new UiSelector()
                .packageName(CALC_PACKAGE).resourceId("plus")).click();
        device.findObject(new UiSelector()
                .packageName(CALC_PACKAGE).resourceId("three")).click();
        device.findObject(new UiSelector()
                .packageName(CALC_PACKAGE).resourceId("equals")).click();

        // Verify the result = 5
        UiObject result = device.findObject(By.res(CALC_PACKAGE, "result"));
        assertEquals("5", result.getText());
    }
    

Cómo ejecutar pruebas de UI Automator en un dispositivo o emulador

Puedes ejecutar pruebas de UI Automator desde Android Studio o desde la línea de comandos. Asegúrate de especificar AndroidJUnitRunner como el panel de instrumentación predeterminado en tu proyecto.

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