Un test flaky pasa y falla con el mismo código. Entre las dos ejecuciones no cambió nada salvo el tiempo, el orden o algo fuera del test en lo que nadie estaba pensando.

Es peor que un test que siempre falla, y no es una forma de hablar. Un test que siempre falla se arregla antes de comer. Un test que falla una vez de cada diez le enseña a todo un equipo a pulsar el botón de reintentar, y una vez que existe esa costumbre, cada fallo genuino tiene que competir con el ruido por la atención de alguien.

Lo que cuesta la inestabilidad

Entrena a la gente para ignorar el rojo. La primera vez que falla una compilación, alguien investiga. En la vigésima falsa alarma, la respuesta es reintentar y encogerse de hombros, y una regresión real recibe el mismo gesto.

Se esconde entre la multitud. Una suite con una docena de tests poco fiables falla con la frecuencia suficiente para que nadie pueda distinguir un fallo poco fiable de uno verdadero sin abrirlo, así que nadie lo abre.

Hace la suite más lenta y menos fiable a la vez. Cada reintento cuesta minutos; la duda cuesta más.

De dónde viene la inestabilidad

Casi toda es una de cinco cosas.

Esperar lo que no es. Con diferencia, lo más común en pruebas de navegador. El test pregunta si algo es visible mientras todavía está apareciendo, o comprueba un estado al que la interfaz llega una fracción de segundo después. Pasa en una máquina rápida y falla en un servidor de compilación cargado, que es justo la máquina en la que no puedes depurar.

Dependencia del orden. Un test que solo pasa después de que otro se haya ejecutado, porque ese otro creó el registro, fijó el estado o dejó algo detrás. Ejecuta la suite en otro orden, o en paralelo, y se viene abajo.

Estado compartido. Una fila de base de datos, un valor en caché, un archivo en disco, un reloj. Dos tests que usan el mismo fixture acabarán ejecutándose lo bastante juntos como para interferir.

El tiempo. Todo lo que comprueba la fecha de hoy cruza la medianoche tarde o temprano; todo lo que tiene un tiempo de espera falla bajo carga; todo lo que depende del orden de dos eventos sin garantía de orden es una moneda al aire que aún no has notado.

El mundo exterior. Un test que llega a una red real, a un servicio de terceros real o a un reloj real ha tomado prestada la disponibilidad de otra persona.

El error que oculta la mayoría de ellos

El hilo común en los casos de tiempo merece enunciarse aparte, porque cambia cómo escribes la comprobación.

Esperar una transición
Comprobar que el botón muestra su etiqueta en reposo, mientras la animación que limpia el estado anterior sigue en marcha
Esperar un estado
Comprobar que la clase "copied" ha desaparecido, y solo entonces que la etiqueta en reposo está ahí

Lo primero es una carrera entre la paciencia del framework de pruebas y el temporizador de la interfaz. Pasa en una máquina tranquila y falla en una ocupada, y el mensaje de error es confuso en lugar de informativo: el elemento está presente, con el texto correcto, y sencillamente aún no es visible.

Ese ejemplo es real: es un test de esta misma base de código, y falló exactamente una vez, en un runner cargado, en un commit que cambiaba dos archivos de imagen.

Qué hacer con uno

No lo arregles esperando más. Subir un tiempo de espera global ralentiza la ruta de fallo de todos los demás tests y esconde la siguiente carrera en lugar de eliminarla. Equivale a subir el volumen de la música.

Tampoco lo borres, al menos no lo primero. Un test flaky suele estar señalando algo real: una carrera genuina en el producto, una interfaz que informa de que ha terminado antes de haber terminado, un recurso compartido que usan dos cosas. Arreglar el test a veces significa arreglar la aplicación.

Cuarentena, luego arreglo, con fecha límite. Sácalo de la suite que bloquea para que deje de enseñar a la gente a ignorar el rojo, y ponle una fecha. La cuarentena sin fecha límite es borrarlo con pasos de más y peor conciencia.

Cuéntalos. Un equipo que no sabe decir cuántos tests flaky tiene se enterará cuando el número sea grande. Si tu runner registra los reintentos, ese es el número que hay que vigilar.

Cuando la inestabilidad es culpa del producto

A veces el test tiene razón y el software es poco fiable: una petición que de vez en cuando llega desordenada, una interfaz que dice “guardado” antes de que el guardado termine, un trabajo que normalmente acaba antes de que la página se recargue.

Esos son defectos reales y son un suplicio de reportar, porque por definición no ocurren cada vez. Lo que los hace reproducibles para otra persona es el contexto de la ejecución que falló, y no una descripción de las diez que pasaron.

Session Replay

Extensión gratuita de Chrome. Un clic en la página que se está portando mal captura la captura de pantalla, la consola y el registro de red, y te devuelve un enlace para pegar en el ticket.

Instalar la extensión

Para un defecto intermitente, di con qué frecuencia ocurre y qué estabas haciendo cuando ocurrió: “tres veces en unos veinte intentos, siempre justo después de guardar” es mucho más útil que la descripción de una sola vez. La guía del informe de error cubre el resto.

La versión corta

Un test flaky falla con código sin cambios, y su coste real es que enseña a la gente a ignorar los fallos. La mayoría son un test que espera una transición en lugar de un estado, una dependencia del orden o estado compartido. Arregla la causa en lugar del tiempo de espera, pon en cuarentena con fecha límite en lugar de indefinidamente, y tómate en serio la posibilidad de que el test tenga razón y sea el software lo poco fiable.