Pruebas inestables: Qué las causa y cómo solucionarlas definitivamente
Una prueba que falla al azar le enseña a su equipo a ignorar las fallas. Aprenda cómo confirmar que una prueba no es correcta, localizar la causa real y solucionarla, con demostraciones que muestran lo que realmente funciona.
- Una prueba inestable pasa y falla en el mismo código, sin cambios.
- Las causas habituales son el tiempo, el estado compartido, los datos inestables, los servicios externos y las diferencias ambientales.
- Reemplace las inactividades fijas con esperas para una condición específica y proporcione a cada prueba sus propios datos.
- Los reintentos pueden ocultar errores reales. Ponga en cuarentena una prueba deficiente, corríjala dentro de un plazo o elimínela.
La compilación se pone roja, alguien hace clic en “volver a ejecutar”, pasa y todos siguen adelante. Parece inofensivo, pero las pruebas deficientes destruyen lentamente lo más valioso que tiene un conjunto de pruebas: la confianza de la gente en él. Cuando las fallas suelen ser ruido, los errores reales también se detectan.
Esta guía explica por qué las pruebas se vuelven inestables y cómo solucionar cada causa adecuadamente. Ejecutamos las demostraciones de sincronización y estado compartido en Python 3.13, y los números que se muestran provienen de esas ejecuciones. Son pequeñas demostraciones diseñadas para hacer que la descamación sea fácil de ver, por lo que sus propias tasas de fracaso serán diferentes.
¿Qué es una prueba escamosa?
Una prueba inestable da resultados diferentes en el mismo código. Pasa en una ejecución y falla en la siguiente, aunque no haya cambiado nada en el producto. La prueba puede tener un error o el código puede contener un error intermitente real. Esa segunda posibilidad es exactamente la razón por la que no deberías simplemente descartarlas.
¿Por qué las pruebas inestables son tan costosas?
- Confianza perdida. Una vez que las personas asumen que el rojo significa “probablemente inestable”, dejan de investigar fallas reales.
- Tiempo perdido. Los desarrolladores pasan horas volviendo a ejecutar canalizaciones y examinando fallas que no tienen nada que ver con su cambio.
- Entrega más lenta. Fusiona y libera la espera en las reposiciones, lo que socava el objetivo de CI/CD.
- Errores ocultos. Las condiciones de carrera en el producto pueden parecerse al ruido de prueba, y esos son los que perjudican en la producción.
Causas comunes de un vistazo
| causa | Signo típico | Solución habitual |
|---|---|---|
| Temporización y asíncrono | Falla más cuando CI está ocupado | Espere una condición |
| Estado compartido | Falla según el orden de la prueba | Aislar datos, restablecer estado |
| Fechas y aleatoriedad | Falla en determinados momentos o días | Controla el reloj y la semilla. |
| Selectores frágiles | Falla después de cambios en la interfaz de usuario | Utilice localizadores estables |
| Servicios externos | Falla cuando una dependencia es lenta | Burlarse de ellos o aplastarlos |
| Diferencias ambientales | Pasa localmente, falla en CI | Alinear con contenedores |
| Límites de recursos | Falla en ejecuciones paralelas | Sintonizar a los trabajadores, arreglar la contención |
Causa 1: sincronización y sueño fijo
Esta es la causa más común. Una prueba supone que el trabajo en segundo plano finalizará después de un retraso fijo. Para ver el efecto, escribimos una pequeña prueba en la que el trabajo tarda hasta 2 ms, mientras que la prueba espera solo 1 ms antes de verificar:
def flaky_timing():
result = {}
def work():
time.sleep(random.uniform(0, 0.002))
result["done"] = True
threading.Thread(target=work).start()
time.sleep(0.001) # hope it has finished
assert result.get("done")
La solución es esperar la condición real, con un límite superior generoso:
def fixed_timing():
result = threading.Event()
def work():
time.sleep(random.uniform(0, 0.002))
result.set()
threading.Thread(target=work).start()
assert result.wait(timeout=1) # continues as soon as it is ready
Ejecutamos cada versión 200 veces:
| Versión | Fallos de 200 |
|---|---|
| Sueño fijo | 102 |
| Espere una condición | 0 |
El resultado es más que solo confiabilidad. La versión fija avanza en el momento en que se realiza el trabajo, por lo que suele ser también más rápida. Una suspensión más prolongada haría que la primera versión fallara con menos frecuencia, pero cada ejecución sería más lenta y aún podría fallar en una máquina de CI ocupada.
En las pruebas de navegadores se aplica el mismo principio. Utilice la espera automática de Dramaturgo, o Esperas de selenio explícitas, en lugar de time.sleep:
# Playwright: retries until true or a timeout is reached
expect(page.get_by_text("Order placed")).to_be_visible()
# Selenium: an explicit wait for a condition
WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "confirmation"))
)
Causa 2: estado compartido y orden de prueba
Cuando las pruebas comparten datos, una puede afectar a la otra y el resultado depende del orden en que se ejecutan. Estas dos pruebas utilizan una lista compartida:
store = []
def test_add():
store.append("x")
assert len(store) == 1
def test_empty():
assert store == []
| Orden | test_add | test_empty |
|---|---|---|
| vacío, luego agregue | Pase | Pase |
| agregar, luego vaciar | Pase | fallar |
Nuestra ejecución confirmó esto, y es por eso que una suite puede pasar meses y luego fallar cuando alguien agrega ejecuciones paralelas o un complemento de orden aleatorio. La cura es el aislamiento:
- Proporcione a cada prueba datos nuevos, utilizando un dispositivo que los configure y los limpie. Nuestro tutorial de pytest muestra cómo hacerlo.
- Cree registros únicos, como un correo electrónico con un sufijo aleatorio, en lugar de un “usuario de prueba” compartido.
- Envuelva las pruebas de la base de datos en una transacción y revertirlas, donde su configuración lo permita.
- Realice pruebas en orden aleatorio con regularidad, para que estos problemas aparezcan temprano.
Causa 3: Fechas, zonas horarias y aleatoriedad
Un examen que se aprueba toda la semana puede suspender un sábado, a medianoche o el último día del mes. Haga del tiempo una entrada, en lugar de leer el reloj dentro de su lógica:
def is_weekend(today=None):
today = today or datetime.date.today()
return today.weekday() >= 5
is_weekend(datetime.date(2026, 11, 7)) # True (a Saturday)
is_weekend(datetime.date(2026, 11, 9)) # False (a Monday)
Esas pruebas dan la misma respuesta cualquier día. Del mismo modo, establezca una semilla fija para valores aleatorios en las pruebas y regístrela cuando utilice datos aleatorios para poder reproducir una falla. No asuma un orden particular a partir de conjuntos, diccionarios de fuentes externas o consultas de bases de datos sin ORDER BY.
Causa 4: Selectores frágiles
Una prueba que encuentra un botón con div:nth-child(3) > span falla con cualquier ajuste de diseño y puede fallar solo para algunos tamaños o variantes de pantalla. Prefiera localizadores basados en lo que ven los usuarios o atributos de prueba dedicados. Nuestra guía de Localizadores de dramaturgos explica el orden para probarlos. Las animaciones son otra fuente frecuente de problemas, así que considere desactivarlas en entornos de prueba.
Causa 5: Servicios y redes externos
Si una prueba llama a un proveedor de pagos real o a una API de terceros, entonces su resultado depende del tiempo de actividad y los límites de velocidad de otra persona. Simule esas llamadas para la mayoría de las pruebas y mantenga una pequeña cantidad de verificaciones de contrato separadas contra el servicio real, para poder saber si cambia. Nuestra guía para pruebas unitarias versus pruebas de integración explica cómo dividirlas.
Consejo: “Aprueba localmente, falla en CI” normalmente significa una diferencia en velocidad, zona horaria, recursos o versiones. La ejecución de pruebas en la misma imagen de contenedor localmente y en CI elimina muchas de ellas. Nuestro tutorial de Docker Compose puede resultar útil.
Cómo diagnosticar una prueba escamosa
- Confírmelo. Ejecute la prueba muchas veces sin cambios. Con pytest, funciona un bucle de shell simple y un complemento como
pytest-repeatle brinda--count=100. - Registrar evidencia. Guarde registros, capturas de pantalla y seguimientos de cada falla. El visor de huellas de Dramaturgo es particularmente útil aquí.
- Enfatiza las condiciones. Ejecútalo con CPU limitada, en paralelo o en orden aleatorio. Hacer que se suspenda con más frecuencia hace que sea más fácil estudiar.
- Pruébelo solo. Si siempre pasa solo pero falla en la suite, sospeche del estado compartido.
- Busque patrones. Compruebe qué máquinas, horas del día y pruebas vecinas están involucradas.
- Compruebe el producto. Pregunte honestamente si esto podría ser una condición de carrera real en su aplicación.
¿Los reintentos automáticos son una solución?
Los reintentos son una red de seguridad útil a corto plazo y no son una cura. Un único reintento automático en CI puede mantener el trabajo en movimiento mientras usted investiga, pero una prueba que necesita un reintento para pasar todavía le dice algo. Peor aún, un error de producto real e intermitente que falla el 20% de las veces normalmente pasará desapercibido en un reintento.
Utilice reintentos con estas reglas: limítelos a uno o dos, registre cada prueba que necesite una y trate esa lista como su lista de tareas pendientes. Una prueba informada como “aprobada al reintentar” debería contar como un problema a investigar, no como un éxito.
Un proceso de equipo para pruebas inestables
- Medir. Realice un seguimiento de qué pruebas fallan y luego pase la misma confirmación. Muchas herramientas de CI y reporteros de pruebas pueden demostrar esto.
- Cuarentena. Saque una prueba inestable confirmada de la ejecución de bloqueo, para que deje de interrumpir a todos, pero continúe ejecutándola y rastreándola.
- Asigna un propietario y una fecha límite. Una cuarentena sin fecha límite se convierte en un cementerio.
- Reparar o eliminar. Si no vale la pena arreglar una prueba, elimínela y verifique que su cobertura aún exista en otros lugares.
- Prevenir. Ejecute nuevas pruebas varias veces antes de fusionarlas. Una prueba que falla una vez cada cincuenta carreras se detecta mejor ahora que en seis meses.
Trate una prueba inestable como un error con prioridad y no como ruido de fondo. La gravedad y la prioridad también se aplican aquí, como se explica en gravedad frente a prioridad. Si la inestabilidad es generalizada, lea sobre errores de automatización de pruebas más amplios y considere si tiene demasiadas pruebas a nivel del navegador.
Conclusión
Las pruebas inestables parecen aleatorias, pero rara vez lo son. Casi todo el mundo se reduce a una suposición sobre el tiempo, el orden, los datos o el entorno que no siempre es cierta. Elija la prueba que falle con más frecuencia en su proceso esta semana. Ejecútelo cincuenta veces, encuentre la suposición y arréglelo para siempre. Una solución real le enseña a un equipo que más de cien reposiciones lo harán.
Preguntas frecuentes
¿Qué es una prueba escamosa?
Una prueba inestable pasa algunas ejecuciones y falla en otras sin ningún cambio en el código. La causa puede estar en la prueba, el medio ambiente o el producto.
¿Qué causa las pruebas inestables?
Las causas más comunes son suposiciones de tiempo, estado compartido entre pruebas, selectores inestables, servicios externos, manejo de fechas y diferencias entre CI y máquinas locales.
¿Debo eliminar las pruebas inestables?
Primero intenta arreglarlos. Si una prueba tiene poco valor y es costosa de arreglar, eliminarla está bien, pero asegúrese de que su cobertura importante exista en otro lugar.
¿Debo volver a intentar las pruebas fallidas automáticamente?
Como red de seguridad limitada, sí, pero registre cada reintento e investíguelo. Los reintentos por sí solos ocultan los problemas y pueden permitir que errores intermitentes reales lleguen a producción.
¿Cómo encuentro pruebas inestables?
Busque pruebas que fallen y aprueben la misma confirmación, utilizando su historial de CI o una herramienta de informes. También puede ejecutar pruebas sospechosas muchas veces en un bucle.
sigue aprendiendo
¿Necesitas ayuda con pruebas, WordPress o SEO?
Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.
Más guías sobre testing, IA y desarrollo moderno
¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.
Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas
Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…
Leer artículo →DevOpsExplicación de las variables de entorno: archivos .env, secretos y configuración bien hechos
Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…
Leer artículo →Desarrollo de softwareExplicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429
La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …
Leer artículo →Automatización de pruebasBurlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan
Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…
Leer artículo →Pruebas de softwareExplicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%
Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…
Leer artículo →Bases de datos y SQLDELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno
DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…
Leer artículo →Bases de datos y SQLVistas SQL explicadas: cree, use y evite los errores, con ejemplos
Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…
Leer artículo →Desarrollo de softwareInterfaz TypeScript versus tipo: diferencias reales, probadas con el compilador
Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…
Leer artículo →Desarrollo de softwarePython *args y **kwargs explicados: funciones flexibles con ejemplos claros
Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…
Leer artículo →Desarrollo de softwareFunciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas
Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…
Leer artículo →Inteligencia artificial¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar
¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…
Leer artículo →DevOpsMonitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos
Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…
Leer artículo →DevOpsDocker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta
Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…
Leer artículo →Automatización de pruebasTDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una
TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …
Leer artículo →Pruebas de softwarePruebas de Aceptación de Usuario (UAT): Proceso, Lista de Verificación y Ejemplos Reales
Explicación de las pruebas de aceptación del usuario: quién las ejecuta, en qué se diferencia del control de calidad, un proceso de siete pasos, una…
Leer artículo →Bases de datos y SQLValores NULL de SQL explicados: IS NULL, COALESCE, NULLIF y las trampas que se deben evitar
Los valores NULL de SQL se explican con las consultas que ejecutamos: lógica de tres valores, IS NULL, COALESCE, NULLIF, cómo los agregados, las uni…
Leer artículo →Bases de datos y SQLDeclaración SQL CASE WHEN: sintaxis, ejemplos reales y errores comunes
CASO SQL CUANDO se explicó con las consultas que ejecutamos: formularios simples y buscados, orden de condiciones, pivotes con SUMA, trucos COUNT, c…
Leer artículo →Desarrollo de software¿Qué es JWT? Tokens web JSON explicados con un ejemplo práctico
¿Qué es JWT? JSON Web Tokens explicó: las tres partes, un token creado y manipulado en el código que ejecutamos, JWT vs sesiones, almacenamiento, to…
Leer artículo →Desarrollo de softwareExplicación del bucle de eventos de JavaScript: pila de llamadas, microtareas y temporizadores con ejemplos
El bucle de eventos de JavaScript explicado con el código que ejecutamos: pila de llamadas, colas de tareas y microtareas, por qué setTimeout 0 no e…
Leer artículo →Desarrollo de softwareExplicación de los generadores y el rendimiento de Python: ahorre memoria con ejemplos reales
Los generadores de Python y el rendimiento se explican con el código que ejecutamos: cómo se pausan y reanudan, midieron el ahorro de memoria, canal…
Leer artículo →Inteligencia artificial¿Qué son las incrustaciones? Los vectores y la similitud del coseno se explican de forma sencilla
¿Qué son las incrustaciones? Una guía en lenguaje sencillo sobre vectores, similitud de cosenos y búsqueda semántica, con un pequeño ejemplo que eje…
Leer artículo →DevOpsImplementación azul-verde versus implementación canaria: diferencias, compensaciones y cómo elegir
Explicación de la implementación azul-verde versus canaria: cómo funciona cada uno, costo, riesgo y reversión, agrupación estable de usuarios que pr…
Leer artículo →Automatización de pruebasPruebas inestables: qué las causa y cómo solucionarlas para siempre
Pruebas inestables explicadas con demostraciones que realizamos: las causas principales, cómo confirmar y diagnosticar una, correcciones adecuadas p…
Leer artículo →Pruebas de softwarePruebas de carga frente a pruebas de estrés: diferencias, métricas y ejemplos de k6
Explicación de las pruebas de carga frente a las pruebas de estrés: qué prueba cada una, pruebas de picos y de absorción, las métricas que importan,…
Leer artículo →Pruebas de softwareCaso de prueba frente a escenario de prueba: diferencias clave, ejemplos y cuándo utilizar cada uno
Caso de prueba versus escenario de prueba explicado: definiciones claras, una tabla en paralelo, una función de restablecimiento de contraseña tomad…
Leer artículo →Bases de datos y SQLOptimización de consultas SQL: lea planes de ejecución y corrija consultas lentas, con tiempos
Optimización de consultas SQL probada en un millón de filas: lea planes de ejecución, agregue los índices correctos, corrija filtros no sargables, u…
Leer artículo →Bases de datos y SQLSQL UNION vs UNION ALL: diferencias, ejemplos y rendimiento probado
SQL UNION vs UNION ALL explicado con consultas que ejecutamos: duplicados, las reglas que siguen ambos, un error de datos real, rendimiento medido, …
Leer artículo →Desarrollo de softwareTutorial de JavaScript Fetch API: GET, POST, manejo de errores y tiempos de espera
Un tutorial de JavaScript Fetch API probado en un servidor local: GET y POST, por qué 404 no arroja, un asistente reutilizable, cadenas de consulta,…
Leer artículo →Desarrollo de softwareManejo de archivos Python: lectura, escritura, CSV, JSON y pathlib con ejemplos
El manejo de archivos Python se explica con el código que ejecutamos: modos abiertos, lectura de archivos grandes, CSV, JSON, pathlib, archivos bina…
Leer artículo →Desarrollo de softwareClases y objetos de Python: tutorial de programación orientada a objetos con ejemplos reales
Clases y objetos de Python explicados con el código que ejecutamos: __init__, métodos, atributos de clase versus instancia, herencia, propiedades, m…
Leer artículo →Inteligencia artificial¿Qué es un modelo de lenguaje grande? Cómo funcionan los LLM, explicados sin exageraciones
¿Qué es un modelo de lenguaje grande? Los LLM explicados en lenguaje sencillo: tokens, incrustaciones, atención, entrenamiento, ventanas de temperat…
Leer artículo →DevOpsComandos de Linux para principiantes: la guía esencial de terminal con ejemplos
Comandos de Linux para principiantes, agrupados por tarea: navegación, archivos, visualización de registros, grep, canalizaciones, permisos, proceso…
Leer artículo →Desarrollo de softwareGit merge vs rebase: diferencias, ejemplos y cuándo usar cada uno
Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.
Leer artículo →Desarrollo de softwareList comprehension en Python: sintaxis, ejemplos y cuándo evitarla
List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.
Leer artículo →