Utilizando Golden Tests como herramienta de QA visual.

Juan Diego Campuzano
Bancolombia Tech
Published in
8 min readFeb 8, 2024

En el dinámico campo del desarrollo de aplicaciones Flutter, cada nueva herramienta que promete mejorar la eficiencia y la calidad captura nuestra atención. En este artículo, abordaremos el concepto de los Golden Tests y cómo su implementación no solo aceleró nuestros tiempos de entrega, sino que también elevó la coherencia y calidad de los componentes que entregamos.

¿Qué es el Golden Testing?

Según la definición que nos da Flutter, el Golden testing es una técnica utilizada en el ámbito de las pruebas de software para verificar que la salida de una parte del software permanezca sin cambios con el tiempo. En un golden test, se compara una captura actual de un widget o un grupo de widgets, contra una versión “golden” previamente almacenada que se usará como imagen de referencia. la comparación se hace pixel a pixel, por lo que la mínima modificación visual en un componente o pantalla es detectada por el test.

Ejemplo:

Para facilitar la comprensión de esta definición, tomaré como ejemplo una app bastante sencilla que consta de una sola pantalla con un poco de texto y una imagen, tal como se puede observar a continuación:

Supongamos que después de desarrollar esta pantalla, pasamos por un proceso de certificación con el equipo de QA, y acordamos que la pantalla y sus componentes están ok.

Ahora, para garantizar que la pantalla no experimente cambios visuales no deseados, ya sea debido a ajustes accidentales o a actualizaciones de paquetes, realizamos una captura del resultado actual, utilizando el paquete nativo de Flutter para tests, flutter_test. ¿Cómo haremos esto? Es bastante simple, veámoslo a continuación:

testWidgets('Golden test mi pantalla principal', (tester) async {
await tester.pumpWidget(const MyApp());
  //Assert
expectLater(
find.byType(MyApp),
matchesGoldenFile(
'goldens/main.png',
),
);
});

Se trata de una prueba de Widget convencional; la diferencia está en que nuestro assert no tiene la forma habitual de “expect” y en su lugar, se utilizará “expectLater”. **Este último permite manejar comportamientos asíncronos para verificar eventos futuros o flujos de datos no sincrónicos. Otra diferencia es la función matchesGoldenFile, en la cual Flutter se encarga de tomar una captura del widget que le pasamos en el tester y compararla contra la imagen que le especificamos en la ruta.

En nuestro caso, esa imagen aún no existe porque apenas vamos a tomar la captura del golden para guardarla y tenerla como referencia en el futuro. Una vez escribimos nuestro test, Flutter nos ofrece un comando para generar los golden y asegurar que queden almacenados en la ruta que especificamos en la función matchesGoldenFile. El comando es el siguiente:

flutter test --update-goldens

Este comando recorre los tests que tengan la función matchesGoldenFile y toma las capturas de pantalla de los componentes que estamos renderizando, para guardarlas en la ruta que le especificamos. Entonces, una vez ejecutamos el comando, obtenemos lo siguiente:

Y nuestro archivo main.png se verá de la siguiente manera:

Claramente se ve muy diferente a la pantalla que desarrollamos y esto tiene una razón de ser, veamos por qué:

  1. Tipografías: las tipografías personalizadas o que vienen por defecto en la aplicación, pueden dibujarse de manera diferente en las diferentes plataformas, incluso entre versiones de Flutter. Por ejemplo, un archivo golden generado en Windows con una tipografía específica, probablemente será diferente al generado por otro sistema operativo. De hecho, en la misma plataforma, si el golden se generó con una versión de Flutter anterior, puede fallar y requerir una captura actualizada. Para evitar estas situaciones, Flutter utiliza una tipografía por defecto llamada ‘Ahem’ que consiste en rectángulos del mismo tamaño, garantizando que el test será agnóstico a la plataforma en la que se está realizando.
  2. Imágenes: en general, en las pruebas de Flutter no se cargan las imágenes. Es decir, en un widget test las imágenes normalmente no se cargan porque el entorno es diferente, comparado con el entorno de compilación o desarrollo, donde no se renderiza la aplicación completa, sino solo una parte de ella. Sin embargo, sí es posible cargar las imágenes en las pruebas mediante la carga en caché antes de ejecutarlas.

Caso hipotético

Ya que aclaramos la definición de lo que es el Golden Testing, hablemos de un caso de uso hipotético donde este tipo de pruebas entran en acción.

Supongamos que, después de un tiempo y por diferentes razones, a la pantalla que les mostré anteriormente le actualizamos los textos y la aplicación queda de la siguiente forma:

Teniendo en cuenta este nuevo diseño de la pantalla, veamos como quedó el golden:

En la imagen golden se aprecia notablemente la reducción de texto, por lo tanto, si en ese momento ejecutamos el test con esta modificación y consideramos la primera imagen Golden que obtuvimos, la herramienta detectará estos cambios y, en consecuencia, generará una carpeta llamada “Failures”. En esta carpeta se incluirán 4 imágenes:

Nota: Los failures empiezan con el nombre del golden con el que se realizó la prueba. Para este caso: “main”.

  • main_testImage: esta va a hacer la captura del componente y permite ver cómo está actualmente.
  • main_masterImage: el golden que tomó como referencia para hacer la comparación vs. el componente actual.
  • main_maskedDiff: esta es la diferencia número uno. Aquí la herramienta toma las dos imágenes de arriba y las superpone para resaltar las diferencias.
  • main_isolatedDiff: esta es la diferencia número dos. Es similar a la anterior y permite ver en dónde detectó la diferencias exactamente (lo que no haya cambiado no aparece en la imagen).

Enfoque

Teniendo claro cómo funcionan las Golden Test en Flutter, ¿cuál fue el enfoque que le dimos desde el Sistema de Diseño?

El objetivo inicial era encontrar una herramienta que permitiera a los desarrolladores realizar el QA visual de los componentes que ellos mismos creaban y pensamos que esto sería posible utilizando los Golden Test. En nuestro caso, los “goldens” serían las imágenes de ejemplo que el equipo de diseño nos proporciona en el Figma del componente, y las compararíamos con lo que se realiza en desarrollo.

Guiados por este enfoque y la necesidad identificada, comenzamos una investigación para determinar si existía algún paquete que se ajustara a nuestras exigencias. En el proceso encontramos varios paquetes que agregaban nuevas funcionalidades a lo que ya estaba nativamente en flutter_test; sin embargo, ninguno de ellos suplía completamente nuestro requerimiento. Por lo tanto, decidimos crear nuestro propio paquete tomando como referencia los que habíamos visto anteriormente.

bc_golden_plugin

Así nació bc_golden_plugin, un paquete que busca reducir el tiempo en las pruebas de QA, asegurar una mayor calidad en los componentes y encontrar una estrategia para ejecutar las pruebas en el pipeline, sin sobrecargar el repositorio de imágenes.

Volviendo nuevamente al ejemplo que planteamos inicialmente, esta vez haremos la captura del golden utilizando el paquete bc_golden_plugin, en lugar del nativo de Flutter. Al hacerlo, obtenemos la siguiente imagen:

Ahora, podemos ver que las tipografías y las imágenes se ven correctamente en la imagen generada como Golden. A continuación, veamos cómo queda el fragmento de código:

bcGoldenTest(
'Bc Golden Plugin pantalla principal',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'golden_plugin',
widget: const MyApp(),
tester: tester,
device: iPhone13,
);
},
);

Este es un ejemplo de cómo serían las pruebas utilizando el paquete bc_golden_plugin. Ahora son un poco más sencillas que las pruebas de widgets normales. La función bcWidgetMatchesImage se encarga de ejecutar las configuraciones necesarias para renderizar todo correctamente. Además, se puede hablar sobre las configuraciones de dispositivos.

Cuando tengamos esto, hagamos nuevamente el ejemplo de modificar el diseño inicial, aumentando el texto, removiendo la imagen y quitando el botón inferior, para ver qué sucede cuando hacemos la comparación de ambas imágenes. Nuestra aplicación tendría el siguiente aspecto:

Una vez ejecutemos el golden test, el paquete nos va a devolver las siguientes imágenes:

Test image

Master image

Diferencia 1

Diferencia 2

Aquí observamos que el test reconoce la ausencia de la imagen del diseño inicial y el considerable aumento en la cantidad de texto. Sin embargo, hay otra característica que debemos tener en cuenta, pues es complejo desarrollar un componente que tenga una diferencia del 0%. Por lo tanto, una de las funcionalidades que agregamos es la posibilidad de establecer un límite de diferencia para las pruebas. Dicho esto, al concluir el test termina, el paquete nos devuelve el siguiente mensaje:

Se encontró una diferencia de 25.09843237331389%, pero es un valor aceptable, dado que el porcentaje de aceptación es de 35.0%

En este caso, el porcentaje de diferencia encontrado fue del 25% aproximadamente. Hemos definido un porcentaje de aceptación del 35%, por lo tanto, el test nos indicará que se ha realizado con éxito. Si porcentaje fuera menor, el test fallará y nos mostrará el porcentaje de diferencia encontrado, junto con las cuatro imágenes que hemos visto anteriormente.

Otra funcionalidad que consideramos fue evitar llenar el repositorio con imágenes. Para lograrlo, agregamos los archivos .png de los golden en el archivo .gitignore y configuramos las pruebas para que apunten a una imagen inexistente. Así, al ejecutar el pipeline, un script de Dart se encarga de descargar las imágenes en tiempo de ejecución, antes de realizar las pruebas.

Gracias a esta herramienta hemos mejorado la calidad de los componentes que entregamos a nuestros usuarios y continuamos promoviendo la autonomía al momento de desarrollar nuevos componentes.

Si quieres revisar un poco más sobre este paquete, te invito a que le des un vistazo al repositorio en Github https://github.com/bancolombia/bc_golden_plugin.

--

--

Juan Diego Campuzano
Bancolombia Tech

SSR front end developer with flutter, also a cloud enthusiast.