Bibliotecas y herramientas para probar diferentes tamaños de pantalla

Android proporciona una variedad de herramientas y APIs que pueden ayudarte a crear pruebas para diferentes tamaños de pantalla y ventana.

Anulación de configuración de dispositivo

El elemento componible DeviceConfigurationOverride te permite anular los atributos de configuración para probar varios tamaños de pantalla y ventana en los diseños de Compose. La anulación de ForcedSize se adapta a cualquier diseño en el espacio disponible, lo que te permite ejecutar cualquier prueba de IU en cualquier tamaño de pantalla. Por ejemplo, puedes usar un factor de forma de teléfono pequeño para ejecutar todas las pruebas de IU, incluidas las pruebas de IU para teléfonos grandes, dispositivos plegables y tablets.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Figura 1: Uso de DeviceConfigurationOverride para ajustar el diseño de una tablet dentro de un dispositivo de factor de forma más pequeño, como en \*Now in Android*.

Además, puedes usar este elemento componible para configurar la escala de fuentes, los temas y otras propiedades que quizás desees probar en diferentes tamaños de ventana.

Robolectric

Usa Robolectric para ejecutar pruebas de IU basadas en vistas o en Compose en la JVM de forma local, sin necesidad de dispositivos ni emuladores. Puedes configurar Robolectric para que use tamaños de pantalla específicos, entre otras propiedades útiles.

En el siguiente ejemplo de Ahora en Android, Robolectric está configurado para emular un tamaño de pantalla de 1,000 x 1,000 dp con una resolución de 480 dpi:

@RunWith(RobolectricTestRunner::class)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(qualifiers = "w1000dp-h1000dp-480dpi")
class NiaAppScreenSizesScreenshotTests { ... }

También puedes configurar los calificadores desde el cuerpo de la prueba como se hace en este fragmento del ejemplo de Now in Android:

val (width, height, dpi) = ...

// Set qualifiers from specs.
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")

Ten en cuenta que RuntimeEnvironment.setQualifiers() actualiza los recursos del sistema y de la aplicación con la nueva configuración, pero no activa ninguna acción en las actividades activas ni en otros componentes.

Puedes obtener más información en la documentación de Configuración del dispositivo de Robolectric.

Dispositivos administrados por Gradle

El complemento de Android para Gradle en el caso de los dispositivos administrados por Gradle (GMD) te permite definir las especificaciones de los emuladores y dispositivos reales en los que se ejecutan tus pruebas instrumentadas. Crea especificaciones para dispositivos con diferentes tamaños de pantalla para implementar una estrategia de prueba en la que se deban ejecutar ciertas pruebas en ciertos tamaños de pantalla. Cuando usas GMD con integración continua (CI), puedes asegurarte de que se ejecuten las pruebas adecuadas cuando sea necesario, así como aprovisionar e iniciar emuladores, y simplificar la configuración de CI.

android {
    testOptions {
        managedDevices {
            devices {
                // Run with ./gradlew nexusOneApi30DebugAndroidTest.
                nexusOneApi30(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Nexus One"
                    apiLevel = 30
                    // Use the AOSP ATD image for better emulator performance
                    systemImageSource = "aosp-atd"
                }
                // Run with ./gradlew  foldApi34DebugAndroidTest.
                foldApi34(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Pixel Fold"
                    apiLevel = 34
                    systemImageSource = "aosp-atd"
                }
            }
        }
    }
}

Puedes encontrar varios ejemplos de GMD en el proyecto testing-samples.

Firebase Test Lab

Usa Firebase Test Lab (FTL), o un servicio similar de granja de dispositivos, para ejecutar tus pruebas en dispositivos reales específicos a los que es posible que no tengas acceso, como dispositivos plegables o tablets de diferentes tamaños. Firebase Test Lab es un servicio pagado con un nivel gratuito. FTL también admite la ejecución de pruebas en emuladores. Estos servicios mejoran la confiabilidad y velocidad de las pruebas de instrumentación, ya que pueden aprovisionar dispositivos y emuladores con anticipación.

Para obtener información sobre el uso de FTL con GMD, consulta Ajusta tus pruebas con dispositivos administrados por Gradle.

Prueba el filtrado con el ejecutor de pruebas

Una estrategia de prueba óptima no debería verificar lo mismo dos veces, por lo que la mayoría de las pruebas de IU no necesitan ejecutarse en varios dispositivos. Por lo general, debes filtrar las pruebas de IU ejecutando todas o la mayoría de ellas en un factor de forma de teléfono y solo un subconjunto en dispositivos con diferentes tamaños de pantalla.

Puedes anotar determinadas pruebas para que se ejecuten solo en determinados dispositivos y, luego, pasar un argumento a AndroidJUnitRunner con el comando que las ejecuta.

Por ejemplo, puedes crear diferentes anotaciones:

annotation class TestExpandedWidth
annotation class TestCompactWidth

Úsalos en diferentes pruebas:

class MyTestClass {

    @Test
    @TestExpandedWidth
    fun myExample_worksOnTablet() {
        ...
    }

    @Test
    @TestCompactWidth
    fun myExample_worksOnPortraitPhone() {
        ...
    }

}

Luego, puedes usar la propiedad android.testInstrumentationRunnerArguments.annotation cuando ejecutes las pruebas para filtrar aquellas específicas. Por ejemplo, si usas dispositivos administrados por Gradle:

$ ./gradlew pixelTabletApi30DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

Si no usas GMD y administras emuladores en CI, primero asegúrate de que el emulador o dispositivo correcto esté listo y conectado y, luego, pasa el parámetro a uno de los comandos de Gradle para ejecutar pruebas instrumentadas:

$ ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

Ten en cuenta que Espresso Device (consulta la siguiente sección) también puede filtrar pruebas mediante las propiedades del dispositivo.

Dispositivo Espresso

Usa Espresso Device para realizar acciones en emuladores durante pruebas de cualquier tipo de pruebas de instrumentación, incluidas las de Espresso, Compose o UI Automator. Estas acciones pueden incluir configurar el tamaño de la pantalla o activar o desactivar estados o posiciones plegables. Por ejemplo, puedes controlar un emulador de dispositivo plegable y configurarlo en modo de mesa. Espresso Device también contiene reglas y anotaciones JUnit para requerir ciertas funciones:

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {

    @get:Rule(order=1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    @get:Rule(order=2) val screenOrientationRule: ScreenOrientationRule =
        ScreenOrientationRule(ScreenOrientation.PORTRAIT)

    @Test
    fun tabletopMode_playerIsDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

Ten en cuenta que el dispositivo Espresso aún se encuentra en la etapa Alfa y tiene los siguientes requisitos:

  • Complemento de Android para Gradle 8.3 o versiones posteriores
  • Android Emulator 33.1.10 o versiones posteriores.
  • Dispositivo virtual de Android que ejecute el nivel de API 24 o uno posterior

Pruebas de filtro

Espresso Device puede leer las propiedades de los dispositivos conectados para permitirte filtrar pruebas mediante anotaciones. Si no se cumplen los requisitos con anotaciones, se omitirán las pruebas.

Anotación RequiresDeviceMode

Se puede usar la anotación RequiresDeviceMode varias veces para indicar que una prueba se ejecutará solo si todos los valores DeviceMode son compatibles con el dispositivo.

class OnDeviceTest {
    ...
    @Test
    @RequiresDeviceMode(TABLETOP)
    @RequiresDeviceMode(BOOK)
    fun tabletopMode_playerIdDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

Anotación RequiresDisplay

La anotación RequiresDisplay te permite especificar el ancho y la altura de la pantalla del dispositivo mediante clases de tamaño, que definen buckets de dimensiones según las clases de tamaño de ventana oficiales.

class OnDeviceTest {
    ...
    @Test
    @RequiresDisplay(EXPANDED, COMPACT)
    fun myScreen_expandedWidthCompactHeight() {
        ...
    }
}

Cambiar el tamaño de las pantallas

Usa el método setDisplaySize() para cambiar el tamaño de las dimensiones de la pantalla durante el tiempo de ejecución. Usa el método junto con la clase DisplaySizeRule, que garantiza que se deshagan los cambios realizados durante las pruebas antes de la siguiente prueba.

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) val displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    @Test
    fun resizeWindow_compact() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.COMPACT,
            heightSizeClass = HeightSizeClass.COMPACT
        )
        // Verify visual attributes or state restoration.
    }
}

Cuando cambias el tamaño de una pantalla con setDisplaySize(), no se ve afectada la densidad del dispositivo. Por lo tanto, si una dimensión no cabe en el dispositivo de destino, la prueba falla con un UnsupportedDeviceOperationException. Si quieres evitar que se ejecuten pruebas en este caso, usa la anotación RequiresDisplay para filtrarlas:

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) var activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) var displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    /**
     * Setting the display size to EXPANDED would fail in small devices, so the [RequiresDisplay]
     * annotation prevents this test from being run on devices outside the EXPANDED buckets.
     */
    @RequiresDisplay(
        widthSizeClass = WidthSizeClassEnum.EXPANDED,
        heightSizeClass = HeightSizeClassEnum.EXPANDED
    )
    @Test
    fun resizeWindow_expanded() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.EXPANDED,
            heightSizeClass = HeightSizeClass.EXPANDED
        )
        // Verify visual attributes or state restoration.
    }
}

Prueba de restauración de estado

La clase StateRestorationTester se usa para probar la restauración de estado de los componentes componibles sin volver a crear actividades. Esto hace que las pruebas sean más rápidas y confiables, ya que la recreación de actividades es un proceso complejo con varios mecanismos de sincronización:

@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
    val stateRestorationTester = StateRestorationTester(composeTestRule)

    // Set content through the StateRestorationTester object.
    stateRestorationTester.setContent {
        MyApp()
    }

    // Simulate a config change.
    stateRestorationTester.emulateSavedInstanceStateRestore()
}

Biblioteca de Window Testing

La biblioteca de Window Testing contiene utilidades para ayudarte a escribir pruebas que se basen en funciones relacionadas con la administración de ventanas o verifiquen ellas, como la incorporación de actividades o funciones plegables. El artefacto está disponible a través del repositorio de Maven de Google.

Por ejemplo, puedes usar la función FoldingFeature() para generar un FoldingFeature personalizado, que puedes usar en las vistas previas de Compose. En Java, usa la función createFoldingFeature().

En una vista previa de Compose, puedes implementar FoldingFeature de la siguiente manera:

@Preview(showBackground = true, widthDp = 480, heightDp = 480)
@Composable private fun FoldablePreview() =
    MyApplicationTheme {
        ExampleScreen(
            displayFeatures = listOf(FoldingFeature(Rect(0, 240, 480, 240)))
        )
 }

Además, puedes emular funciones de pantalla en las pruebas de IU con la función TestWindowLayoutInfo(). En el siguiente ejemplo, se simula un objeto FoldingFeature con una bisagra vertical HALF_OPENED en el centro de la pantalla y, luego, se verifica si el diseño es el esperado:

Redactar

import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        composeTestRule.setContent {
            MediaPlayerScreen()
        }

        val hinge = FoldingFeature(
            activity = composeTestRule.activity,
            state = HALF_OPENED,
            orientation = VERTICAL,
            size = 2
        )

        val expected = TestWindowLayoutInfo(listOf(hinge))
        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)

        composeTestRule.waitForIdle()

        // Verify that the folding feature is detected and media controls shown.
        composeTestRule.onNodeWithTag("MEDIA_CONTROLS").assertExists()
    }
}

Vistas

import androidx.window.layout.FoldingFeature.Orientation
import androidx.window.layout.FoldingFeature.State
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val activityRule = ActivityScenarioRule(MediaPlayerActivity::class.java)

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        activityRule.scenario.onActivity { activity ->
            val feature = FoldingFeature(
                activity = activity,
                state = State.HALF_OPENED,
                orientation = Orientation.VERTICAL)
            val expected = TestWindowLayoutInfo(listOf(feature))
            windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)
        }

        // Verify that the folding feature is detected and media controls shown.
        onView(withId(R.id.media_controls)).check(matches(isDisplayed()))
    }
}

Puedes encontrar más muestras en el proyecto WindowManager.

Recursos adicionales

Documentación

Ejemplos

Codelabs