Screenshots para tus tests
Probablemente te haya pasado: pasaste horas (incluso días) maquetando esta UI impecable, esa que construiste mano a mano con el equipo de diseño y estás satisfecho con el resultado. Ahí está, una interfaz limpia, responsiva, con los colores y las proporciones deseadas hecha realidad por tu código.
Si bien nada en código es rígido o está escrito en piedra (de ahí el prefijo soft) la experiencia te enseña que debes dejar alguna salvaguarda para tu UI, especialmente cuando trabajas en un equipo grande con un design system en constante evolución; es muy fácil para un colega (o incluso para nosotros mismos en el futuro) cambiar algún color/fuente de la librería de UI compartir y estropear algo de alguien más*. Ten todo esto en mente mientras debes sacar tiempo para armar los tests, navegar hasta la pantalla que estás integrando, setearla en el estado específico que necesitas en este escenario y volver a repetir todo esto con cada estado a fin de tener certeza que tu solución está funcionando para cada permutación posible…
Tiene que haber una mejor manera 🤔 (no tendría sentido hacerte leer hasta acá si no la hubiese después de todo)
Te presento el screenshot testing
Si bien Apple da soporte nativo para objetos lógicos, desde el principio del desarrollo iOS existió un gap para el coverage de testing relacionados con los objetos visuales. El concepto es sencillo: hacer tests unitarios entorno a elementos de UI por código, es decir, generar una representación gráfica (un .png) de nuestras vistas y compararla con una imagen de referencia para verificar.
¿Cómo hacemos en PedidosYa para no volvernos locos cuando debemos elaborar un nuevo feature? Un pequeño “truco” son las pruebas de snapshot mencionadas anteriormente. Hay varios sabores de éstas, conozco colegas que no les gusta incluir librerías de terceros y arman sus propios mecanismos:
Hace ya algunos años Facebook liberó el pod de su librería interna que cumplía este mismo propósito (hoy día mantenida por el equipo de desarrollo de Uber en otro repo). Sin embargo el enfoque de este artículo es en otra librería más reciente, hecha totalmente en Swift a diferencia de sus predecesoras: snapshot testing. Seamos pragmáticos y hagamos uso de ella, después de todo cuenta con buen soporte de la comunidad y está muy bien documentada.
Al final del día debe existir un balance entre forzar estándares y dejar espacio para la creatividad e innovación, bien sea que hagas tus pruebas desde 0 o te apoyes en librería de terceros, lo realmente importante es que las tengas.
¡Mucho ruido y pocas nueces Mauri!
Supongamos que un caso puntual que tuvimos en algún momento en la app: formateo de Labels. Debíamos generar Labels con íconos al principio, al final y tachado (con colores custom). Bastantes estados para poner un Label con múltiples buildeos en el interin para probar que en efecto luzca como quieres la UI así que es un caso ideal para probar nuestro enfoque de snapshots.
Una vez que tengamos la librería agregada a nuestro proyecto, escribimos nuestra prueba con el texto tachado
La primera vez que corremos la prueba mostrada arriba arroja un error al no encontrar una imagen de referencia con la cual hacer la comparación, esto a su vez genera una imagen referencia. La siguiente vez que corremos la prueba obtenemos nuestro esperado diamante verte ✅
Snapshot testing no sólo te permite hacer pruebas de componentes individuales, incluso nos deja hacer capturas de ViewControllers
enteros. Supongamos que en una vista demo tenemos todas las variaciones de nuestro Label formateable:
Para citar un ejemplo en concreto, algún tiempo atrás tuve que hacer una migración de código algo delicada para el formulario de búsqueda de direcciones. Suena como algo sin importancia pero en situaciones cuando alguno de nuestros usuarios no tiene su GPS activo y se encuentra en un lugar fuera de alguna de sus direcciones almacenadas (o directamente está deslogueado), dicho formulario se convierte en el punto de entrada a la app. Sin dirección, no hay delivery después de todo.
Pueden ser varias las permutaciones de dicha pantalla, por lo cual esta tarea era ideal para aplicar snapshot testing y agilizar la salida sin sacrificar calidad o introducir regresiones.
Notas finales
Debemos tener cuidado por ejemplo en las siguientes situaciones:
- Algún
ViewController
/componente de UI está cubierto por la snapshots ve alterado y/o agregado algún comportamiento y dicha alteración no está a su vez cubierta por prueba(s) unitaria(s). Esto generará falsos positivos y falsa confianza en nuestros deploys, pese a seguir contando con el diamante verde de nuestra suite de tests. - El coverage por sí solo no es una medida confiable para la calidad ni cobertura de nuestro code base, de no tener claro esto los snapshots nos van a arrojar coverage sumamente altos en caso de testear
ViewControllers
- No recomiendo usar snapshots en UI que esté en estado de rediseño frecuente (varias iteraciones al día por dar cifra). Suena exagerado el número pero durante un rediseño de design system, es mejor desistir de ellos hasta lograr una versión estable y posteriormente dejar el snapshots con estados válidos.
Habiendo dejado en claro los aspectos no tan favorables, terminemos resumiendo el lado positivo de este enfoque:
- Es muy económico (rápido) probar y validar estados de UI. Para quienes hemos luchado con las pruebas de UI nativas que provee Xcode, conocemos lo lentas que pueden ser. Todo test lento eventualmente dejará de correrse y un test no corrido es igual a un test no escrito -no sirve de nada-
- Deja cierta garantía automatizada que cualquier cambio de UI es intencionado, de lo contrario fallarán las validaciones al comparar con las snapshots guardadas.
No existe silver bullet en esto de la programación así que tampoco debemos abusar de las snapshots, entender que es un herramienta más en nuestro arsenal diario de desarrollo y usarlas con criterio.
*: en PedidosYa implementamos buenas prácticas de integración continua, un ejemplo de ello es que código no puede ser mergeado a producción sin pasar por Code Review, sin embargo buscamos optimizar constantemente estos procesos manuales (enriqueciendo nuestra suite de tests y elaborando scripts para no olvidarnos de tediosos pasos repetitivos). Mientras más cosas se automatizan, mayor tiempo conseguimos para enfocarnos en trabajo creativo y desafiante.