Llevar a cabo una prueba de la interfaz de usuario (IU) que involucre interacciones con usuarios en varias apps te permite verificar que tu app se comporte correctamente cuando el flujo de usuarios cruce a otras apps o a la IU del sistema. Un ejemplo de ese tipo de flujo de usuarios puede ser una aplicación de mensajería que permite al usuario ingresar un mensaje de texto, inicia el selector de contactos de Android para que el usuario pueda seleccionar destinatarios a quienes enviar el mensaje y luego devuelve el control a la app original para que el usuario envíe el mensaje.
En esta lección, se explica cómo programar esas pruebas de IU con el framework de prueba de UI Automator que proporciona AndroidX Test.
Las API de UI Automator te permiten interactuar con elementos visibles en un dispositivo, independientemente de la Activity
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 framework de prueba de UI Automator es una API basada en instrumentación y funciona con el ejecutor de pruebas 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, según se describe en Cómo configurar el proyecto para AndroidX Test.
En el archivo build.gradle
del módulo de tu app para 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. Estas sugerencias de optimización se describen en las siguientes dos secciones.
Cómo inspeccionar la IU en un dispositivo
Antes de diseñar la prueba, inspecciona los componentes de la IU que son visibles en el dispositivo. Para asegurarte de que las pruebas de UI Automator puedan acceder a esos componentes, verifica que tengan etiquetas de texto visibles, valores de android:contentDescription
o ambos.
La herramienta uiautomatorviewer
proporciona una interfaz visual práctica para inspeccionar la jerarquía de diseño y ver las propiedades de los componentes de la IU que son visibles en primer plano en el dispositivo.
Esta información te permite crear pruebas más específicas mediante UI Automator. Por ejemplo, puedes crear un selector de IU que coincida con una propiedad visible específica.
Para iniciar la herramienta uiautomatorviewer
, haz lo siguiente:
- Inicia la app de destino en un dispositivo físico.
- Conecta el dispositivo a la máquina de desarrollo.
- Abre una ventana de terminal y navega al directorio
<android-sdk>/tools/
. - Ejecuta la herramienta con el siguiente comando:
$ uiautomatorviewer
Para ver las propiedades de la IU de tu aplicación, haz lo siguiente:
- En la interfaz de
uiautomatorviewer
, haz clic en el botón Device Screenshot. - 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, en el panel superior derecho. - 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 de usuario.
Asegúrate de que se pueda acceder a tu actividad
El framework de prueba de UI Automator brinda un mejor rendimiento en apps que tienen funciones de accesibilidad de Android implementadas. Cuando usas elementos de IU del tipo View
o una subclase de View
del SDK, no necesitas implementar la compatibilidad de accesibilidad, ya que esas clases lo hacen 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 es del SDK, sigue los pasos que se indican a continuación para asegurarte de agregar funciones de accesibilidad a estos elementos:
- Crea una clase concreta que extienda
ExploreByTouchHelper
. - Asocia una instancia de tu nueva clase con un elemento de IU personalizado específico llamando a
setAccessibilityDelegate()
.
Para acceder a pautas adicionales 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 unidades 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 de AndroidJUnitRunner
, proporcionada en AndroidX Test, como el ejecutor de pruebas 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:
- Obtén un objeto
UiDevice
para acceder al dispositivo que deseas probar llamando al métodogetInstance()
y pasándole un objetoInstrumentation
como argumento. - Obtén un objeto
UiObject
para acceder a un componente de la IU que se muestra en el dispositivo (por ejemplo, la vista actual en primer plano) llamando al métodofindObject()
. - Simula una interacción de usuario específica para ejecutar en ese componente de la IU mediante una llamada al método
UiObject
; por ejemplo, llama aperformMultiPointerGesture()
para simular un gesto multitáctil y asetText()
para editar un campo de texto. Puedes llamar a las API de 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. - Tras la ejecución de las 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.
Cómo acceder a los componentes de la IU
El objeto UiDevice
es la forma principal de acceder al estado del dispositivo y manipularlo. 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 la pantalla.
La prueba puede usar el objeto UiDevice
para ejecutar acciones a nivel del dispositivo, como forzar una rotación específica del dispositivo, presionar botones de hardware del pad direccional y presionar los botones de Inicio y Menú.
Se recomienda comenzar la prueba desde la pantalla principal del dispositivo. Desde esa pantalla (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.
El siguiente fragmento de código muestra la forma en que la prueba podría obtener una instancia de UiDevice
y simular la acción de presionar el botón de inicio:
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, la declaración @SdkSuppress(minSdkVersion = 18)
ayuda a garantizar que las pruebas solo se ejecuten en dispositivos con Android 4.3 (API nivel 18) o versiones posteriores, según lo requiera el framework de UI Automator.
Usa el método findObject()
para recuperar un UiObject
que represente una vista que coincida con un criterio de selección determinado. Puedes volver a usar las instancias de UiObject
que creaste en otras partes de la prueba de la app, según sea necesario. Ten en cuenta que el framework de UI Automator busca una coincidencia en la pantalla actual cada vez que la prueba usa una instancia UiObject
para hacer clic en un elemento de la IU o consultar una propiedad.
En el siguiente fragmento, se muestra la forma en que la prueba podría crear 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(); }
Cómo especificar un selector
Si deseas acceder a un componente de IU específico de una app, usa la clase UiSelector
, que representa una consulta de elementos específicos de la IU que se muestra actualmente.
Si se encuentra más de un elemento coincidente, el primero de la jerarquía de diseño se mostrará como el UiObject
de destino.
Cuando creas un UiSelector
, puedes encadenar varias propiedades para definir mejor la búsqueda. Si no se encuentra ningún elemento de IU que coincida, se muestra una UiAutomatorObjectNotFoundException
.
Puedes usar el método childSelector()
para anidar varias instancias de UiSelector
. En el siguiente ejemplo de código, se ilustra la especificación en la prueba de una búsqueda para hallar la primera ListView
en la IU que se muestra actualmente y, luego, de una búsqueda 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, es recomendable que uses 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 de una barra de herramientas). Los selectores de texto son frágiles y pueden provocar fallas de prueba si hay cambios menores en la IU. Además, es posible que no se escalen entre diferentes idiomas, por lo que podría no haber coincidencias con strings traducidas.
Puede ser útil especificar el estado del objeto en los criterios del selector. Por ejemplo, si deseas seleccionar una lista de todos los elementos marcados para desmarcarlos, llama al método checked()
con el argumento establecido en true
.
Cómo ejecutar acciones
Una vez que la prueba haya obtenido un objeto UiObject
, puedes llamar a los métodos de la clase UiObject
para ejecutar interacciones del usuario en el componente de IU que representa 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 el objeto a coordenadas arbitrarias.-
setText()
: establece el texto en un campo editable después de borrar el contenido del campo. Por el contrario, el métodoclearTextField()
borra el texto existente en un campo editable. -
swipeUp()
: ejecuta la acción de deslizar hacia arriba enUiObject
. Del mismo modo, los métodosswipeDown()
,swipeLeft()
yswipeRight()
ejecutan las acciones correspondientes.
El framework de prueba de UI Automator te permite enviar un Intent
o iniciar una Activity
sin usar comandos de shell mediante la obtención de un objeto de Context
a través de getContext()
.
En el siguiente fragmento, se muestra el uso en la prueba de 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); }
Cómo ejecutar acciones en colecciones
Usa la clase UiCollection
si deseas 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 IU secundarios, como una vista de diseño que contenga elementos de IU secundarios.
En el siguiente fragmento de código, se ilustra la construcción en la prueba de 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();
Cómo ejecutar 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 desplazarse para verlo.
En el siguiente fragmento de código, se muestra cómo simular el desplazamiento hacia abajo en el menú Configuración y el 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();
Cómo verificar los resultados
InstrumentationTestCase
extiende TestCase
para que puedes usar métodos Assert
estándar de JUnit y probar que los componentes de la IU en la app muestren los resultados esperados.
En el siguiente fragmento, se especifica el código que puede incluir la prueba para ubicar varios botones en una app de calculadora, hacer clic en ellos por 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 ejecutor 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.
Muestras
- BasicSample: ejemplo básico de UI Automator