Re-Ejecutando escenarios de prueba fallidos con Cucumber en procesos de despliegue de software

Actualizado: 5 nov 2021



El contexto

Uno de los retos más grandes en la ejecución de pruebas automatizadas, es diseñar estrategias que nos ayuden ahorrando tiempo de ejecución. En el caso de pruebas costosas como las de UI, es común apoyarnos de estrategias de mapeo que nos ahorren unos cuantos milisegundos descubriendo objetos en pantalla o también, incluir estratégicamente esperas implícitas y aún mejor las explícitas para evitar tiempos de ejecución no deseados. ¿Qué pasa entonces cuando corremos nuestros escenarios de prueba de forma desatendida y al menos uno de ellos falla?… nos obliga a ejecutar nuevamente todo el proceso y perder todo el avance de lo que corrió exitosamente, haciendo que esa ejecución se vuelva un dolor de cabeza al tomar el doble de tiempo y a veces más. Imagina que el error es una posible inestabilidad en el ambiente de pruebas y no un error de aplicación, imagina que la aplicación no ha cambiado desde la ultima ejecución de pruebas, sería bastante frustrante para ti y el equipo. Para solucionar esto, te presentaré cómo usar el plugin rerun de Cucumber en una suite de pruebas construida con SerenityBDD que nos ayudará a correr sólo aquellos escenarios que fallaron en nuestra última ejecución.


SerenityBDD es un framework que provee la capacidad de generar documentación comprensible, en tiempo de ejecución y de una manera sencilla. Al integrarlo con Cucumber, debemos comprender que para escenarios desarrollados con Java, tendremos una integración adicional con JUnit que finalmente permitirá ejecutar un archivo .feature a través de un runner. Cuando ejecutamos de forma manual los runners, tenemos todo el control de decidir los escenarios específicos a correr, pero cuando las pruebas son ejecutadas por un proceso automatizado en un pipeline la intención es ejecutar siempre todas las pruebas, y cuando nos encontramos con ejecuciones que fallan constantemente por agentes externos como ambientes de integración inestables, solo resta volver a ejecutar el set completo. El resultado de esa re-ejecución independiente de si 1 ó 10 escenarios han fallado no es óptimo en tiempo teniendo en cuenta que no es error de aplicación ni de la prueba en sí misma. Una excelente estrategia es ejecutar únicamente los escenarios que fallaron. En este artículo te mostraré como hacerlo con el plugin rerun de Cucumber.


El problema

Tomemos el siguiente caso para ilustrar nuestro problema

Tenemos un pipeline que ejecuta nuestro set de pruebas para una aplicación que tiene un costo de aproximadamente 2 horas. Cuando la aplicación no ha sufrido cambios en el código fuente y fallan algunos escenarios como los ilustrados en la imagen lo único que podemos hacer es volver a esperar las 2 horas de re-ejecución para darle continuidad al proceso.



La solución


Como ya he mencionado, al usar Cucumber y Serenity se facilitan los pasos a seguir.

Paso 1: Configura los runner para que use el plugin rerun de Cucumber



Paso 2: Crea un runner que se ocupe de ejecutar el paquete de escenarios fallidos



Opcional:Puedes aplicar el plugin de rerun en el runner que crearás para la ejecución de los escenarios fallidos, en caso que vuelva a fallar te quedará el registro para una nueva ejecución.



Paso 3: En caso de fallo debes programar la ejecución del runner que creaste en el paso 2, para el ejemplo deberás ejecutar RerunFailedScenarios. Si usas gradle, el comando será el siguiente.


gradle --tests "test.app.runners.RerunFailedScenarios"


¿Cómo funciona?

  • El plugin creará el archivo de los escenarios fallidos de la ejecución en la ruta que especifiques en la configuración del mismo:

plugin = {"rerun:ruta/test/fallidos.txt"},


Para el ejemplo creará el archivo MyTestRerun.txt



  • El archivo será alimentado con la ubicación del feature y la línea donde inicia cada escenario fallido usando el separador dos puntos (:) así:



  • Ten cuidado, si todos los runner son configurados con el mismo archivo de salida ya que se reescribirá y quedará con la información del último runner fallido, en este caso recomiendo que uses una nomenclatura para el nombramiento del archivo de salida, por ejemplo usando el nombre del feature que ejecutas.



  • El runner que creaste en el Paso 3 para los escenarios fallidos ejecutará todos los escenarios que fallaron y quedaron almacenados en la ruta

features = "@target/rerunFailed"


Opcional: Puedes especificar un único archivo que quieras ejecutar de la siguiente forma:


features = "@target/rerunFailed/FailedScenarios.txt"


Recuerda que para pruebas de este tipo, por lo general siempre vamos a requerir de la experiencia y análisis de un automatizador de pruebas que pueda definir el tipo de fallo. Re-ejecutar los escenarios que han fallado por error de aplicación no será de ayuda. Sin embargo, para hacer el proceso más automático, podemos (en caso de usar gradle) aprovechar el lifecycle de la tarea de “test” para agregar un paso al final de la ejecución de la misma en el cual, si ha fallado la tarea, se ejecute de nuevo con los escenarios fallidos. Esta última consideración, teniendo en cuenta que es necesario que esa tarea determine que escenarios fallaron por entes externos a la aplicación y las pruebas en sí mismas. Para esto, es recomendable (aprovechando las bondades de Serenity) hacer uso correcto de las excepciones y catalogándolas con los tipos de errores correctos (“compromised”). En la búsqueda de la automatización del proceso encontramos la perfección del mismo.