wandres.dev
UI ADAPTATIVA · plegables, tablets y XR

Probar lo adaptativo: emuladores, previews y checklist

Lo adaptativo introduce una explosión combinatoria que ninguna estrategia de prueba manual resiste: tallas de ancho y de alto, orientaciones, posturas de plegado, escalas de fuente, modos de color y densidades se multiplican entre sí. Esta lección organiza el banco de pruebas en cuatro capas de coste creciente —previsualizaciones anotadas, pruebas de composición con anulación de configuración, pruebas instrumentadas que mueven el dispositivo y verificación manual dirigida—, explica qué defecto captura cada una y cierra con una checklist de calidad por talla que convierte una intuición difusa en criterios de aceptación.

⏱ 19 min

El diseño adaptativo tiene un problema de verificación que no tienen los demás temas de este recorrido: el espacio de estados es un producto cartesiano. Tres o cinco tallas de ancho, tres de alto, dos orientaciones, tres posturas de plegado, un rango continuo de escalas de fuente, dos modos de color y varias densidades no se suman, se multiplican, y el resultado son centenares de combinaciones de las que solo un puñado se prueba jamás. La respuesta ingenua es probar más, lo que se agota en una semana. La respuesta correcta es organizar la verificación en capas de coste creciente, asignar a cada capa la clase de defecto que sabe capturar y elegir en cada una un conjunto pequeño de configuraciones que cubra el espacio sin recorrerlo. Esta lección construye ese banco de pruebas de abajo arriba y termina con la checklist que convierte se ve bien en tablet en una lista de criterios que otra persona puede verificar sin discutir.

🎯 Al terminar esta lección sabrás
  • Construir un conjunto de previsualizaciones que exponga los defectos de geometría en tiempo de diseño.
  • Escribir pruebas de composición que fuercen tamaño, escala de fuente y postura sin necesidad de hardware.
  • Simular pliegues y cambios de ventana en pruebas instrumentadas con las utilidades de prueba de la plataforma.
  • Aplicar una checklist de aceptación por talla con criterios objetivos.

El bucle corto: previsualizaciones multiconfiguración

La capa más barata y la más rentable es la que actúa antes de compilar nada. Las anotaciones múltiples de previsualización generan varias vistas de un mismo composable con las configuraciones más relevantes ya definidas: un juego de tallas de pantalla que cubre teléfono, plegable, tablet y escritorio, un juego de escalas de fuente que llega hasta el extremo del escalado no lineal, y variantes de modo claro y oscuro. Aplicarlas cuesta una línea por composable y detecta la mayoría de los recortes y desbordamientos antes de que existan.

@PreviewScreenSizes
@PreviewFontScale
@Composable
private fun PantallaPrincipalPreview() {
    MiTema { PantallaPrincipal(estado = EstadoDePrueba.completo) }
}

Para casos que el catálogo no cubre hay especificación libre de dispositivo, y ahí conviene añadir dos configuraciones que casi nadie prueba y que rompen aplicaciones con regularidad: la ventana muy ancha y muy baja, que es un teléfono en horizontal o una ventana de escritorio aplastada, y la ventana muy estrecha dentro de una pantalla grande, que es lo que produce el modo multiventana.

Hay dos condiciones para que esta capa funcione. La primera es que el composable previsualizado reciba su clase de tamaño como parámetro en lugar de consultarla; si la consulta, la previsualización siempre verá la del entorno de dibujo y las variantes serán decorativas. Es la razón práctica más fuerte para la disciplina de calcular la clase una sola vez en la raíz. La segunda es que exista un juego de datos de prueba con los casos extremos —el título más largo, la lista vacía, el nombre sin espacios— porque una previsualización con datos ideales no revela nada.

💡
Convierte las previsualizaciones en pruebas de regresion visual

El complemento de pruebas de captura de pantalla sobre previsualizaciones convierte este mismo conjunto en una red de regresión: genera imágenes de referencia, las guarda en el repositorio y falla la integración continua cuando una modificación cambia el resultado. El coste marginal es casi nulo porque las previsualizaciones ya están escritas, y el beneficio es que los defectos de geometría dejan de depender de que alguien mire.

Pruebas de composición con la configuración forzada

La segunda capa ejecuta sobre la máquina virtual, sin gestos ni hardware, y sirve para afirmar comportamiento y no solo apariencia. Su instrumento central es el mecanismo de anulación de configuración del dispositivo, que envuelve el contenido bajo prueba y le impone un tamaño, una escala de fuente, un modo de color o una densidad distintos de los del entorno real.

@Test
fun enAnchoExpandidoSeVenLosDosPaneles() {
    composeTestRule.setContent {
        DeviceConfigurationOverride(
            DeviceConfigurationOverride.ForcedSize(DpSize(1024.dp, 800.dp)),
        ) {
            PantallaCorreo()
        }
    }

    composeTestRule.onNodeWithTag("panelLista").assertIsDisplayed()
    composeTestRule.onNodeWithTag("panelDetalle").assertIsDisplayed()
}

Lo valioso de esta capa no es comprobar que algo se ve, sino comprobar invariantes que atraviesan las tallas. Los tres que más defectos capturan son: que el conjunto de acciones disponibles es idéntico en compacto y en expandido, que el número de destinos de navegación no cambia con la forma de la navegación, y que ninguna etiqueta se trunca con la escala de fuente al máximo. Los tres se escriben una vez, se ejecutan en segundos y previenen exactamente las regresiones que más molestan al usuario.

El mismo mecanismo cubre otras dimensiones del producto cartesiano con la misma sintaxis: forzar la escala de fuente para verificar el escalado no lineal, forzar el modo oscuro para comprobar contrastes, forzar la densidad o la forma redonda de la pantalla. Componer varias anulaciones a la vez permite probar la combinación que más defectos produce en la práctica y que casi nadie ejecuta jamás: ventana estrecha con la fuente al máximo.

Existe además el camino de las pruebas locales con calificadores de recursos, que permiten fijar la configuración con la misma sintaxis que las carpetas de recursos y ejecutar sin dispositivo alguno. Es la vía más rápida de todas y encaja bien cuando lo que se verifica es la selección de recursos o la lógica de decisión estructural, más que el resultado visual.

flowchart TD
A[Capa uno previsualizaciones anotadas] --> B[Capa dos pruebas de composicion con configuracion forzada]
B --> C[Capa tres pruebas instrumentadas que mueven el dispositivo]
C --> D[Capa cuatro verificacion manual dirigida]
A --> E[Coste bajo y volumen alto]
D --> F[Coste alto y volumen minimo]
A --> G[Captura recortes y desbordamientos]
B --> H[Captura invariantes entre tallas]
C --> I[Captura perdida de estado en transiciones]
D --> J[Captura problemas de ergonomia y alcance]
style A fill:#a6e3a1,color:#11111b
style D fill:#f38ba8,color:#11111b

Mover el dispositivo de verdad

La tercera capa es la única que captura la clase de defecto más cara: la pérdida de estado durante una transición. Ni una previsualización ni una configuración forzada recrean la actividad, y por tanto ninguna de las dos ve desaparecer el texto a medio escribir al girar.

Aquí hay dos utilidades distintas y complementarias. La primera publica información de disposición falsa en las pruebas, de modo que se puede construir una característica de plegado sintética con la orientación, el estado y los límites que se quieran y observar cómo responde la interfaz. Es la forma de probar el modo mesa y el modo libro sin poseer un plegable de cada fabricante.

@get:Rule val reglaDePliegue = WindowLayoutInfoPublisherRule()

@Test
fun enModoMesaLosControlesBajanAbajo() {
    reglaDePliegue.simulateTabletopMode(composeTestRule)   // pliegue horizontal, medio abierto
    composeTestRule.onNodeWithTag("controles").assertIsDisplayed()
}

La segunda actúa sobre el dispositivo o el emulador y cambia su estado real: gira la pantalla, cambia el tamaño del área de visualización entre las tallas del catálogo y coloca un plegable en cerrado, plano, mesa o libro. Al provocar cambios de configuración auténticos, es la que expone la recreación de la actividad y con ella toda la familia de defectos de continuidad. Su contrapartida es que exige un emulador con las capacidades adecuadas y que es la capa más lenta de las tres automatizadas.

// Cambiar el estado real del dispositivo dentro de la prueba
@Test
fun elFormularioConservaLoEscritoAlGirar() {
    composeTestRule.onNodeWithTag("campoNombre").performTextInput("Adriana")
    onDevice().setScreenOrientation(ScreenOrientation.LANDSCAPE)
    composeTestRule.onNodeWithTag("campoNombre").assertTextContains("Adriana")
}

El banco de emuladores conviene fijarlo por escrito y automatizarlo con dispositivos gestionados por el sistema de construcción, de modo que la integración continua ejecute siempre el mismo conjunto. Un banco razonable tiene cuatro entradas: un teléfono ordinario, un plegable con las dos pantallas, una tablet grande y un dispositivo redimensionable. Este último merece énfasis: permite arrastrar el borde de la ventana en caliente, que es la prueba más brutal y más reveladora que existe, porque somete la aplicación a decenas de cambios de tamaño por segundo y hace visible al instante cualquier decisión tomada una sola vez en el arranque.

⚠️
La transicion es una prueba distinta del estado final

Casi todas las suites verifican estados: en ancho expandido se ven dos paneles, en compacto uno. Ninguna verifica el camino. Escribe al menos una prueba por pantalla importante que ejecute una tarea a medias, provoque el cambio de configuración y afirme que lo escrito, lo seleccionado y lo desplazado siguen ahí. Es la prueba que más defectos reales encuentra de todo el nivel.

Checklist de calidad por talla

La verificación manual sigue siendo necesaria, pero solo aporta si está dirigida. Lo que sigue no es una lista de comprobaciones cosméticas sino un conjunto de criterios de aceptación que se pueden pegar en una tarjeta de trabajo y que otra persona puede evaluar sin ambigüedad.

📱

Compacto

Todo el contenido en una columna sin desplazamiento horizontal. Acciones principales al alcance del pulgar. Nada truncado con la escala de fuente al máximo. Estado íntegro al girar.

📐

Medio

Navegación fuera del borde inferior. Longitud de línea por debajo de setenta caracteres. El segundo panel, si existe, con ancho útil real. Sin franjas vacías mayores que el contenido.

🖥️

Expandido y superiores

Dos o más paneles con reparto ajustable. Contenido con ancho máximo y centrado en lugar de estirado. Recorrido completo con teclado. Atajos y foco visibles.

📖

Plegado

Ningún contenido ni control sobre la banda del pliegue. Postura de mesa o libro aprovechada donde tenga sentido. Sesión intacta al plegar y desplegar en mitad de una tarea.

Cuatro criterios transversales completan la lista y se aplican a todas las tallas. El primero es la paridad funcional: el conjunto de acciones disponibles no depende de la geometría, solo su presentación. El segundo es la continuidad: ningún cambio de tamaño, orientación o postura pierde estado visible para el usuario. El tercero es la densidad honesta: el espacio adicional se emplea en mostrar más contenido o en dar más aire, nunca en ampliar tipografías y controles hasta parecer una aplicación de teléfono estirada. Y el cuarto es la fluidez de la transición: los cambios de disposición están animados y no producen saltos ni fotogramas en blanco.

Merece la pena señalar cómo se usa esta lista en la práctica, porque su valor está más en el momento que en el contenido. Aplicada al final, es una auditoría que produce una lista de defectos y una discusión sobre prioridades. Incorporada a la definición de terminado de cada tarea de interfaz, deja de producir defectos porque impide que se escriban. La diferencia de coste entre ambos usos es de un orden de magnitud.

No se prueban configuraciones, se prueban las fronteras entre ellas

Hay una idea de fondo en todo este aparato de verificación que conviene extraer, porque es lo único de esta lección que seguirá siendo cierto cuando cambien las herramientas concretas. La intuición natural ante un espacio combinatorio es muestrear: si hay centenares de configuraciones posibles, elijamos unas cuantas representativas y probemos ahí. Es un razonamiento razonable y produce suites que pasan siempre mientras los usuarios siguen reportando fallos, y la razón de esa paradoja es que los defectos del diseño adaptativo casi nunca viven dentro de una configuración estable. Viven en el tránsito de una a otra. Una aplicación mal construida se ve perfectamente bien en compacto y perfectamente bien en expandido; lo que hace mal es pasar de uno a otro, y ese paso no es ninguno de los dos puntos que muestreaste. Esto tiene una consecuencia metodológica precisa: el objeto de prueba correcto no es el conjunto de configuraciones sino el conjunto de fronteras entre configuraciones adyacentes, y ese conjunto es más pequeño y mucho más informativo. Las fronteras que importan son pocas y se pueden enumerar: cruzar cada umbral de talla en ambas direcciones, girar en ambos sentidos, plegar y desplegar, entrar y salir de multiventana, conectar y desconectar un teclado o una pantalla externa, y subir la escala de fuente mientras la ventana ya es estrecha. Una docena de tránsitos, cada uno ejecutado en mitad de una tarea con estado sucio, encuentra más defectos que cien configuraciones estáticas verificadas con la aplicación recién abierta. Y hay un motivo estructural para que sea así, que enlaza con todo lo anterior del nivel: cada frontera es exactamente el punto donde el código tomó una decisión —recalcular la clase de tamaño, cambiar de disposición, recrear la actividad, redistribuir los paneles— y toda decisión es un sitio donde alguien pudo asumir algo que solo era cierto en un lado. Probar en el centro de una configuración verifica el resultado de decisiones que ya se tomaron bien; probar en la frontera verifica las decisiones mismas. Quien organiza su trabajo de verificación alrededor de los tránsitos deja de perseguir defectos por talla y empieza a construir una interfaz que es correcta por continuidad, que es una propiedad mucho más fuerte y considerablemente más barata de mantener.

⚔️ Montar el banco de pruebas del nivel
  1. Añade previsualizaciones multitalla y multiescala de fuente a tus tres pantallas principales, con datos de prueba en su caso extremo. Anota cuántos defectos aparecen antes de ejecutar nada.
  2. Escribe tres pruebas de composición con configuración forzada que afirmen paridad de acciones, paridad de destinos de navegación y ausencia de truncamiento con la fuente al máximo.
  3. Simula el modo mesa y el modo libro con información de disposición falsa y verifica que tu interfaz responde a cada postura.
  4. Define un banco de cuatro dispositivos gestionados —teléfono, plegable, tablet y redimensionable— y déjalo ejecutándose en tu integración continua.
  5. Enumera los tránsitos de frontera de tu aplicación y escribe una prueba por cada uno que ejecute el cambio en mitad de una tarea con estado sucio. Empieza por girar con un formulario a medias.