Pruebas y calidad de software

Estrategia de automatización de pruebas: qué automatizar, a qué coste, sin trampas

Avanzado12 min de lecturaActualizado

Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés

Cada test automatizado es un activo que lleva asociado un pasivo de mantenimiento. Esta guía trata del portafolio: elegir qué comprobaciones merecen automatizarse, dar forma a la suite para que el feedback llegue en el orden correcto, gestionar entornos y datos, aplicar una política de tests inestables que la gente cumpla de verdad y escoger métricas que revelen algo.

Lo que aprenderás

  • Evaluar un test candidato frente a su coste de mantenimiento previsible antes de automatizarlo
  • Diseñar una suite por capas con un presupuesto explícito de tiempo de feedback para cada nivel
  • Aplicar una política de tests inestables que preserve la confianza en el build
  • Usar la cobertura y los quality gates como señales y no como objetivos que cumplir
  • Elegir métricas de automatización que cambien decisiones en lugar de decorar dashboards
  • Situar las pruebas exploratorias junto a una suite automatizada y no en su contra

Un test automatizado no sale gratis una vez escrito. Ocupa minutos de build en cada ejecución, hay que actualizarlo cada vez que el comportamiento que cubre cambia legítimamente, fallará de vez en cuando por motivos ajenos al código y alguien tendrá que leerlo años después para averiguar qué estaba protegiendo. Una estrategia de pruebas es, por tanto, una decisión de portafolio: cuáles de estos pasivos merece la pena asumir y en qué proporciones.

Los equipos rara vez fracasan por haber automatizado demasiado poco de lo correcto. Fracasan por haber automatizado muchísimo de lo incorrecto: cientos de tests de navegador que tardan una hora y fallan los martes, tests unitarios que comprueban que se llamó a un mock, cifras de cobertura conseguidas ejercitando getters. Esta guía trata de las decisiones que evitan todo eso, y da por hecho que ya sabes escribir los tests en sí.

Automatizar es invertir

Antes de automatizar una comprobación, estima tres cosas. ¿Con qué frecuencia se ejecutará? Una comprobación que corre en cada commit durante tres años se ejecuta miles de veces; la de una migración puntual, una sola. ¿Cómo de estable es lo que observa? Un test ligado a una regla de negocio sobrevive a los rediseños; uno ligado a un selector CSS o al orden de los campos de un JSON, no. ¿Cuánto costará mantenerla funcionando? Esa es la cifra que la gente olvida, y la que domina el total.

Multiplica esos factores y algunas decisiones conocidas se vuelven obvias. Automatizar una prueba de valores límite sobre una regla de precios es barato y duradero. Automatizar un recorrido completo en el navegador hasta el pago es caro, frágil y aun así compensa: una vez, para el camino más valioso. Automatizar ese mismo recorrido para once variantes de cliente es donde el retorno se vuelve negativo.

Hay cosas que merece la pena automatizar por motivos distintos de la detección de defectos: la preparación de un entorno mediante scripts, un generador de datos, un script que reproduce un ticket de soporte. Automatizar tareas suele aportar más que automatizar comprobaciones, y nunca aparece en un informe de cobertura.

Lo que debe seguir siendo manual: todo lo que requiere criterio (¿se ve bien esta página?, ¿ayuda de verdad este mensaje de error?), todo lo que se ejecuta una sola vez, todo aquello cuyo resultado esperado no puede enunciarse sin ambigüedad y el trabajo exploratorio que se describe al final de esta guía.

Formas: pirámide, trofeo, panal

La pirámide clásica coloca en la base muchos tests rápidos y aislados, por encima menos tests a nivel de servicio y en la punta un puñado de tests end-to-end. Refleja un compromiso real —amplitud de la cobertura frente a velocidad, estabilidad y precisión del diagnóstico— y, para un sistema con una lógica de dominio sustancial, sigue siendo un buen consejo.

El testing trophy ensancha la parte central: una base de análisis estático y comprobación de tipos, un número modesto de tests unitarios, un gran volumen de tests de integración y pocos end-to-end. Encaja en sistemas que son sobre todo composición: un servicio que valida una petición, llama a dos dependencias y escribe una fila casi no tiene lógica que probar unitariamente, y todos sus fallos interesantes están en las fronteras. El panal (honeycomb) hace el mismo planteamiento para microservicios, con predominio de tests integrados y un uso intensivo de tests de contrato para evitar suites end-to-end entre servicios.

Discutir qué diagrama es el correcto es desperdiciar una reunión. La invariante que hay detrás de todos ellos: prefiere el test más estrecho capaz de detectar el fallo que te importa, porque los tests estrechos son más rápidos, más estables y señalan su propia causa. Lo que determina la forma es dónde reside de verdad el riesgo de tu sistema. Un motor de cálculo de impuestos tiene forma de pirámide. Un API gateway, no.

Hay una regla que sobrevive a todas las variantes: los tests end-to-end son un bisturí, no una manta. Mantén unos pocos que cubran los recorridos de negocio críticos, asume que son lentos y no los uses nunca para comprobar una regla que un test más barato podría comprobar.

Niveles y presupuestos de feedback

Decide cuánto puede costar cada nivel y hazlo cumplir.

NivelSe ejecutaPresupuestoContenido
Compilación y análisis estáticoEn cada guardado y cada pushsegundosTipos, linters, detectores de bugs
UnitarioEn cada pushmenos de 2 minutosLógica, límites, ramas de error
IntegraciónEn cada pull requestmenos de 10 minutosPersistencia, serialización, clientes, contratos
End-to-endAl hacer merge a mainmenos de 20 minutosUn puñado de recorridos críticos
No funcionalCada noche o en cada releaselo que haga faltaCarga, resistencia, escaneos de seguridad

Los presupuestos son la estrategia. Cuando un nivel supera su presupuesto, algo tiene que ceder —paralelizar, dividir o bajar tests de nivel— y haber acordado la cifra de antemano convierte eso en una decisión rutinaria y no en un debate. Un pipeline de pull request que poco a poco se pasa de los quince minutos cambia el comportamiento de los desarrolladores: agrupan cambios, cambian de contexto y empiezan a hacer merge a ver qué pasa.

Las convenciones de nombres hacen la separación mecánica. Surefire ejecuta las clases *Test, Failsafe ejecuta las clases *IT en una fase posterior y los valores de @Tag de JUnit permiten incluir o excluir porciones en una misma ejecución. La ejecución en paralelo suele ser la gran mejora más barata que tienes a mano; además obliga a que los tests sean independientes, algo que vale la pena tener de todos modos.

Entornos y datos de prueba

Dos recursos compartidos causan la mayor parte de los problemas operativos de una suite de pruebas, y ambos tienen el mismo remedio: deja de compartirlos.

Entornos. Un único entorno de staging en el que todos los equipos tienen que desplegar antes de ejecutar sus tests se convierte en una cola con hoja de reservas. Sustitúyelo siempre que puedas por dependencias efímeras que arranca el propio test, como se describe en la guía de pruebas de integración. Conserva un entorno compartido para lo que de verdad no se puede contenerizar —un sandbox de un tercero, un mainframe— y trata el tiempo en él como el recurso escaso que es.

Datos de prueba. Un conjunto de datos compartido del que todo depende se vuelve intocable: nadie puede cambiar una fila porque no sabe cuál de doscientos tests se rompería. La alternativa son datos creados por el test que los necesita, mediante builders, y que después se eliminan o se hacen inmunes a colisiones con claves únicas. Para los datos que deben tener la forma de los de producción, usa un generador alimentado con un extracto anonimizado en lugar de una copia: datos de producción en un entorno de pruebas son un incidente de cumplimiento esperando a una auditoría.

Una política de tests inestables que perdure

Un test que falla de forma intermitente sobre código sin cambios no es una molestia menor; es un ataque al valor de toda la suite. En cuanto «vuelve a lanzarlo» se convierte en la respuesta normal al rojo, los fallos reales también se relanzan y la suite deja de ser una señal.

Escribe la política antes de necesitarla, porque en el momento siempre hay una release que sacar:

  1. Detectar. Registra cada resultado de test a lo largo del tiempo, no solo el último. Un test que ha fallado dos veces en las últimas cincuenta ejecuciones sobre código sin cambios es inestable por definición, y no puedes saberlo sin historial.
  2. Poner en cuarentena de inmediato. Muévelo a un job no bloqueante ese mismo día. El build tiene que volver a ser fiable en cuestión de horas, no de sprints.
  3. Abrir un ticket con pruebas. Adjunta las ejecuciones fallidas, los tiempos, la carga de la máquina. La inestabilidad es un defecto con una causa, normalmente estado compartido, una suposición de timing, una dependencia del orden de ejecución o una race condition real en el código de producción.
  4. Arreglar o borrar en un plazo fijo. Dos semanas es una elección habitual. Un test en cuarentena que nadie arregla no aporta ninguna protección y sigue consumiendo minutos de build; borrarlo al menos deja el hueco a la vista.
  5. Seguir el recuento. El número de tests en cuarentena es una métrica de salud. Si sube, la suite se está deteriorando.

Una advertencia que conviene decir claramente: a veces un test inestable delata una race condition real en el código de producción, que tarde o temprano se manifestará como un fallo intermitente en producción. Borrar el test sin investigar es la manera de tapar un bug real.

Cobertura: léela, no la persigas

La cobertura de líneas y de ramas mide qué código se ejecutó mientras corrían los tests. No mide si se comprobó algo, si las aserciones tenían sentido ni si se cubrieron los casos importantes. Una suite puede ejecutar cada línea de un sistema sin comprobar absolutamente nada.

Usada como señal, la cobertura es realmente útil. Ordena por líneas sin cubrir y encontrarás el módulo que nadie se atrevía a tocar, la rama de error que nunca se ha ejecutado, la clase añadida el trimestre pasado sin ningún test. Mira lo que queda sin cubrir y decide si importa.

Usada como objetivo, corrompe. Exige un 80 % y obtendrás un 80 %, conseguido por la vía más barata: tests sobre código generado, bucles que lo ejecutan todo sin comprobar nada y exclusiones de cobertura aplicadas a las partes difíciles. El gate que funciona en la práctica se aplica solo al código nuevo y modificado: no hace falta poner al día a posteriori una base de código legacy, pero no entra nada nuevo sin tests. Es exactamente lo que impone un quality gate de SonarQube sobre el código nuevo, y la guía de análisis estático explica cómo configurarlo.

El mutation testing responde a la pregunta que la cobertura no puede responder: herramientas como PIT modifican el bytecode —invierten una condición, sustituyen un valor de retorno— y vuelven a ejecutar los tests. Una mutación que sobrevive significa que ningún test notó el cambio, lo que identifica aserciones que faltan en lugar de líneas no ejecutadas. Es demasiado lento para ejecutarlo en cada build, y excelente como auditoría periódica de un módulo crítico.

Gates en el pipeline

Un quality gate es un criterio de salida automatizado. Cada uno que añades hace el pipeline más lento y más propenso a bloquear un cambio legítimo, así que elígelos con intención. Un conjunto defendible para un servicio:

  • Compilación con los warnings tratados como errores en el código nuevo.
  • Tests unitarios en verde: sin excepciones y sin saltárselos vía cuarentena.
  • Cobertura de las líneas modificadas por encima de un mínimo acordado.
  • Ningún hallazgo crítico nuevo del análisis estático.
  • Ninguna vulnerabilidad nueva de severidad alta en las dependencias.
  • Tests de integración y verificación de contratos en verde antes del merge.

Dos reglas hacen que los gates sean llevaderos. Primero los gates rápidos, para que los fallos habituales aparezcan en los dos primeros minutos y no en el vigésimo. Y nada de excepciones permanentes: un gate que se puede saltar marcando una casilla se saltará de forma rutinaria en menos de un mes. Si un gate se equivoca tan a menudo que la gente quiere saltárselo, arregla el gate. Cómo encaja todo esto en el pipeline completo se trata en la guía de entrega continua.

Responsabilidad y hábitos

Los tests pertenecen al equipo dueño del código. Un grupo de automatización separado que escribe tests contra el servicio de otro equipo crea un traspaso, una cola y tests que fallan sin que nadie capaz de leerlos esté de guardia. Los testers especialistas son valiosos —en el diseño de pruebas, las sesiones exploratorias, el trabajo no funcional y la construcción del arnés de pruebas compartido que hace más rápidos a todos los demás—, pero no como una fábrica de aserciones externalizada.

Tres hábitos distinguen a los equipos cuyas suites se mantienen sanas:

  • Un build roto es la máxima prioridad. No «alguien le echará un vistazo». O se arregla o se revierte en un plazo corto y acordado.
  • Los tests se revisan como código de producción. La duplicación, los nombres poco claros y el exceso de mocks reciben comentarios. Un test que nadie entiende no se puede mantener.
  • Cada defecto de producción genera un test. Escrito primero, reproduciendo el fallo, antes de la corrección. Esa única práctica va orientando la suite hacia donde de verdad aparecen los defectos en tu sistema, y no hacia donde imaginabas que aparecerían.

Métricas que cambian decisiones

La mayoría de los dashboards de pruebas muestran números sobre los que nadie actúa. Los que merece la pena seguir son aquellos cuya variación desencadena una decisión:

  • Tiempo de feedback por nivel, seguido como tendencia. Sin atención, solo empeora.
  • Causas de los fallos de build, clasificadas en defecto real, test inestable o infraestructura. Si los inestables superan a los reales, la suite tiene un problema de credibilidad.
  • Defectos escapados, es decir, incidencias en producción que un test podría plausiblemente haber detectado. Cada uno plantea la pregunta de qué capa lo dejó pasar.
  • Tasa de fallos en cambios y tiempo medio de recuperación: las métricas de entrega que las pruebas existen para mejorar. Si la suite crece y estas no mejoran, el esfuerzo se está yendo a algún sitio improductivo.
  • Número de tests en cuarentena, como se ha descrito antes.

Números que no vale la pena reportar: el total de tests (más tests no es mejor), la tasa de éxito (debería ser del 100 %; si no, tienes un fallo de política) y las líneas de código de test.

El lugar de las pruebas exploratorias

Las comprobaciones automatizadas verifican cosas en las que ya habías pensado. No pueden encontrar el problema que nadie anticipó, y esa categoría incluye la mayoría de los interesantes: flujos confusos, un mensaje que no tiene sentido en su contexto, un comportamiento técnicamente correcto y comercialmente equivocado.

Las pruebas exploratorias cubren ese hueco y son una actividad que exige oficio, no hacer clic al azar. Se trabajan en sesiones acotadas en el tiempo con un charter escrito («explorar los reembolsos de pedidos enviados parcialmente, buscando estados de los que la interfaz no pueda recuperarse»), notas tomadas durante la sesión y hallazgos comunicados después. Todo lo que encuentran y merece comprobarse para siempre se convierte en un test automatizado; el resto queda como conocimiento sobre el sistema.

Un reparto útil del trabajo: las máquinas vuelven a comprobar lo conocido y las personas investigan lo desconocido. Los equipos que lo automatizan todo y no exploran nada entregan productos que pasan todos los tests y decepcionan a todos los usuarios, y la guía de fundamentos de las pruebas explica por qué eso es un problema de validación y no de verificación.

Puntos clave

  • Cada test automatizado conlleva un pasivo de mantenimiento; decide si la comprobación se ejecutará con suficiente frecuencia y será lo bastante estable como para compensar.
  • Prefiere el test más estrecho capaz de detectar el fallo: ese principio, y no un diagrama de pirámide concreto, es lo que todas las formas intentan aproximar.
  • Da a cada nivel un presupuesto de feedback acordado y hazlo cumplir; un pipeline de pull request lento cambia cómo trabajan los desarrolladores, y siempre a peor.
  • Deja de compartir entornos y datos de prueba; las dependencias efímeras y los datos por test eliminan de un plumazo la mayor parte de los problemas operativos.
  • Aplica una política escrita de tests inestables —detectar, poner en cuarentena, abrir ticket, arreglar o borrar—, porque una suite que la gente relanza en lugar de confiar en ella no vale nada.
  • Lee la cobertura, no la persigas; aplica los gates al código nuevo, audita los módulos críticos con mutation testing y mantén las pruebas exploratorias en la mezcla para lo que la automatización no ve.

Fuentes y lecturas recomendadas

Preguntas frecuentes

¿Qué porcentaje de las pruebas debería automatizarse?
La pregunta presupone un conjunto fijo de comprobaciones que repartir, y no funciona así. La automatización compensa en comprobaciones que se repetirán muchas veces, tienen un criterio de éxito inequívoco y son baratas de mantener. La investigación exploratoria, el juicio sobre la usabilidad y las verificaciones puntuales merecen el tiempo de una persona y nunca compensa automatizarlas. Aspira a automatizar todo lo que se ejecuta en cada commit y deja de pedir un porcentaje.
¿Es un 80 % de cobertura de código un buen objetivo?
Es una observación razonable y un mal objetivo. En cuanto se impone una cifra, la forma más barata de alcanzarla es escribir tests que ejecutan código sin comprobar nada, lo que sube la métrica y baja el valor. Usa la cobertura para encontrar código que ningún test toca, aplica los umbrales a las líneas recién modificadas en vez de a todo el código base y revisa lo que queda sin cubrir en lugar de perseguir un número.
¿Debe el pipeline reintentar automáticamente los tests que fallan?
Reintentar para conseguir un build en verde oculta el fallo y garantiza que la inestabilidad se quede para siempre. Un reintento de alcance limitado puede ser aceptable como medida temporal si cada reintento se registra y se reporta, para que el equipo vea qué tests son inestables y con qué frecuencia. Sin esa visibilidad, los reintentos automáticos convierten un defecto conocido en uno invisible.
¿Quién debe ser dueño de los tests automatizados, los desarrolladores o un equipo de QA?
El equipo dueño del código es dueño de los tests que se ejecutan en su build, porque un test ilegible justo para quienes podrían arreglarlo acaba siendo un bloqueo. Los testers especialistas aportan más valor en el diseño de pruebas, las sesiones exploratorias, las pruebas no funcionales y la creación de herramientas compartidas — no como un grupo aparte que escribe comprobaciones a posteriori sobre el código de otros. Un equipo de automatización separado crea una cola y un traspaso, justo lo que la entrega continua intenta eliminar.