
La suite de tests pasa. Verde en todas las líneas. El pipeline de CI se completa sin un solo fallo. Y entonces alguien de soporte te escribe: “Oye, un cliente dice que no puede completar el registro desde hace tres días”. Miras los tests, están todos verdes. Miras producción, el bug lleva ahí desde el último deploy. Tus tests no fallaron porque nunca estuvieron diseñados para detectar ese defecto.
El primer principio del testing según ISTQB lo deja claro: el testing puede demostrar que existen defectos, pero no puede demostrar que no los hay. Es una distinción sutil pero fundamental. Que tus tests pasen solo significa que los escenarios que has definido funcionan como esperabas. Nada más.
La idea no es nueva. Tiene raíces en la filosofía de la ciencia, concretamente en el principio de falsabilidad de Karl Popper. De la misma forma que un solo cisne negro refuta la afirmación de que todos los cisnes son blancos, un solo bug encontrado demuestra la presencia de defectos. Pero ejecutar un millón de tests sin fallos no demuestra que el software esté libre de bugs.
Dijkstra lo resumió hace décadas de forma memorable: “El testing puede usarse para mostrar la presencia de bugs, pero nunca para mostrar su ausencia”. No era una opinión pesimista, sino una observación técnica sobre los límites inherentes de cualquier proceso de verificación.
Este principio parece teórico hasta que te golpea en producción. Hay patrones que se repiten en prácticamente todos los equipos donde he trabajado.
Un equipo con 2.000 tests y un 95 % de cobertura tiende a sentir que su producto está blindado. Esa sensación es peligrosa. He visto proyectos con métricas de cobertura envidiables que seguían acumulando bugs críticos porque la suite probaba muchas líneas de código pero validaba pocos comportamientos reales. La cobertura mide qué código se ejecuta durante los tests, no qué condiciones se verifican de verdad.
Este es uno de los problemas más frecuentes y más difíciles de detectar en una revisión rápida. Un test que llama a una función y comprueba que no lanza excepción técnicamente cubre esas líneas. Pero si no valida que el resultado sea correcto, que los efectos secundarios se produzcan o que el estado final sea el esperado, ese test es humo. En mi experiencia, cuando haces una auditoría seria de una suite grande, descubres que entre un 10 y un 20 % de los tests no tienen asserts significativos o validan cosas triviales como que el resultado no sea null.
Los tests pasan en el entorno de desarrollo. Pasan en CI. Pero el bug aparece en producción porque hay una diferencia en la configuración del servidor, en la versión de una dependencia transitiva, en los datos reales del usuario o en la concurrencia bajo carga. Tus tests demuestran que el código funciona en las condiciones que has definido. Producción tiene sus propias condiciones, y no siempre coinciden.
Imagina un formulario de registro con validación de email, contraseña y nombre. Tienes tests para email inválido, contraseña corta y campos vacíos. Todos pasan. Pero nadie escribió un test para un email con caracteres unicode válidos según el RFC pero que tu librería de validación no soporta. Nadie probó qué pasa cuando el servicio de envío de emails de confirmación tarda más de 30 segundos. Nadie simuló un registro desde un navegador con JavaScript parcialmente bloqueado. Los tests cubren lo que imaginaste, y los bugs viven donde no imaginaste.
Cuando un equipo olvida que los tests solo prueban presencia de defectos, cae en trampas predecibles.
Aceptar que los tests no garantizan ausencia de bugs no es rendirse. Es ajustar la estrategia para maximizar la detección real.
El mutation testing es la forma más directa de saber si tus tests realmente detectan defectos. Herramientas como Stryker modifican el código de producción de formas controladas, por ejemplo cambiando un > por >=, eliminando una línea o invirtiendo un booleano, y comprueban si algún test falla. Si la mutación sobrevive, tus tests no habrían detectado ese tipo de error.
No necesitas ejecutarlo sobre todo el proyecto. Empieza por los módulos más críticos y ve ampliando. Los resultados suelen ser reveladores: suites con un 90 % de cobertura donde el 40 % de las mutaciones sobreviven.
El testing exploratorio no es “hacer clic por ahí a ver qué pasa”. Es una actividad estructurada donde defines una misión, un tiempo limitado y documentas lo que encuentras. Por ejemplo: “durante 30 minutos voy a explorar el flujo de recuperación de contraseña usando direcciones de email con caracteres especiales y conexiones lentas”.
Este tipo de testing complementa la suite automatizada porque va precisamente donde la automatización no llega: los caminos que nadie pensó en codificar.
Dedica una sesión a revisar los tests de un módulo crítico y clasifica cada assert en tres categorías. Los asserts fuertes validan comportamiento específico y fallarían si el código se comporta de forma incorrecta. Los asserts débiles validan cosas genéricas como que el resultado no sea nulo o que la respuesta tenga un status 200, sin comprobar el contenido. Y los asserts ausentes son tests que ejecutan código pero no comprueban nada significativo.
Si más de un 20 % de tus asserts son débiles o ausentes, tienes un problema de calidad de tests que la cobertura no refleja.
Después de escribir tests para una funcionalidad, párate y hazte una pregunta incómoda: “si tuviera que encontrar un bug aquí, por dónde atacaría?” Piensa en las entradas que no has contemplado, los estados previos que no has configurado, las dependencias externas que has mockeado con respuestas felices y las condiciones de carrera que has ignorado.
En mi experiencia, los bugs más dolorosos de producción viven justo en esos huecos entre lo que probamos y lo que asumimos.
No dependas solo de los tests automatizados. Combina varias capas de detección que se complementen entre sí.
Una suite de tests es como una red de pesca. Atrapa muchas cosas, pero siempre hay peces que pasan entre los huecos. La clave no es pretender que la red es impermeable, sino saber dónde están los huecos más grandes y usar herramientas complementarias para cubrirlos.
La próxima vez que tu suite pase al 100 %, en lugar de sentir alivio, pregúntate: “qué defectos podrían estar ahí fuera y mis tests no son capaces de ver?” Esa pregunta, repetida de forma honesta, es lo que separa a un equipo que confía ciegamente en sus tests de uno que realmente protege su software.
Un ejercicio para esta semana: elige tres tests de tu módulo más crítico y analiza sus asserts. Si alguno solo comprueba que no hay excepción o que el resultado no es nulo, reescríbelo para que valide el comportamiento real. Ese pequeño cambio ya te acerca a una suite que detecta defectos de verdad.
Los scripts E2E necesitan datos sensibles —tokens de API, credenciales, URLs privadas— sin que aparezcan en el código. En JMO Labs hemos añadido variables de script con modo privado: se inyectan automáticamente, se enmascaran en los logs y se acceden con una sintaxis limpia.

Los tests E2E se rompen con cada cambio de interfaz. En JMO Labs construimos un pipeline de 5 fases con IA que planifica, ejecuta, repara selectores, diagnostica fallos y verifica resultados de forma autónoma. La caché de selectores hace que cada ejecución sea más rápida que la anterior.

Playwright no es solo para tests E2E. En JMO Labs lo usamos como motor completo: 9 fases de comprobación, localizador de 9 estrategias con self-healing, grabación de vídeo, testing responsive con viewports reales y accesibilidad con axe-core.