Tests inestables: por qué aparecen y cómo eliminarlos de verdad
Las causas reales de los tests que pasan y fallan sin motivo, cómo diagnosticarlas por orden de probabilidad y por qué añadir reintentos es la peor solución posible.
Qué es exactamente un test inestable
Un test inestable, o flaky, es el que produce resultados distintos en ejecuciones sucesivas sin que hayan cambiado ni el sistema bajo prueba ni el propio test. No es un test lento, ni uno que falla siempre por un defecto conocido: es uno cuyo resultado no es determinista.
Su coste no está en el tiempo que hace perder, sino en lo que destruye: la confianza. En cuanto un equipo aprende que un fallo rojo "probablemente sea el test", empieza a reejecutar sin mirar, y el día que el rojo era un defecto real llega a producción sin que nadie lo haya leído.
Causas por orden de probabilidad
- Sincronización: el test actúa antes de que la aplicación esté lista. Es la causa número uno, y se manifiesta más en máquinas de integración continua, que suelen ir más lentas que el portátil donde se escribió la prueba.
- Datos compartidos: dos pruebas usan el mismo usuario, el mismo pedido o el mismo saldo. Al ejecutarse en paralelo, o en otro orden, se pisan entre ellas.
- Dependencia del orden: una prueba deja un estado del que otra depende sin decirlo. Funciona en la secuencia habitual y se rompe en cualquier otra.
- Entorno: servicios que tardan en levantar, versiones de navegador o driver que cambian, discos llenos, husos horarios, idiomas del sistema.
- Dependencias externas: servicios de terceros con cuota, latencia variable o disponibilidad irregular.
- Aleatoriedad y tiempo: datos generados al azar sin semilla fija, o lógica que depende de la fecha y falla el día uno de mes o en el cambio de hora.
El diagnóstico, paso a paso
- Mide la tasa de inestabilidad por prueba: qué porcentaje de ejecuciones falla sobre el mismo commit. Sin ese dato no puedes priorizar.
- Ejecuta la prueba sospechosa veinte veces en aislamiento. Si nunca falla, el problema es de interacción con otras pruebas: datos u orden.
- Ejecútala en paralelo con el resto. Si falla ahí, ya tienes localizada la colisión.
- Revisa cada espera del test. Cualquier pausa fija es sospechosa por definición.
- Comprueba las evidencias del fallo: captura, log y petición. Si no las tienes, ese es el primer arreglo, porque sin ellas cada diagnóstico empieza de cero.
Arreglos que funcionan
| Causa | Arreglo correcto | Parche que la esconde |
|---|---|---|
| Sincronización | Espera explícita por condición observable | Aumentar el sleep |
| Datos compartidos | Datos propios por ejecución, creados y limpiados por el test | Ejecutar en serie para que no colisionen |
| Dependencia del orden | Setup y teardown completos en cada prueba | Fijar el orden de ejecución |
| Entorno | Entorno reproducible con versiones fijadas y comprobación de arranque | Reejecutar hasta que salga verde |
| Servicio externo | Doble de prueba o virtualización, con contrato verificado aparte | Reintentos indefinidos |
Los reintentos automáticos merecen un párrafo propio. Son útiles como medida temporal y trazada: reintento contado, marcado en el informe y con fecha de revisión. Convertidos en política permanente son un mecanismo para ocultar defectos reales del producto, incluidos los de concurrencia, que son precisamente los que más caros salen en producción.
La cuarentena bien hecha
Cuando una prueba inestable bloquea la pipeline y no hay tiempo de arreglarla en el momento, la respuesta correcta es la cuarentena: se saca del conjunto que corta la entrega, pero se sigue ejecutando y midiendo, se le asigna un responsable y se le pone una fecha límite. Sin esas tres condiciones, la cuarentena es un cajón donde las pruebas van a morir.
Y conviene vigilar el tamaño de ese cajón como una métrica más: si crece mes a mes, el problema no son las pruebas, es que la solución de automatización se está degradando más rápido de lo que se mantiene.