Pruebas que valen lo que cuestan
La prueba no existe para demostrar que el código está bien. Existe para que puedas cambiarlo mañana sin miedo. Ese cambio de objetivo lo reorganiza todo.
Empieza por la pregunta equivocada y llegarás a una suite cara e inútil.
La pregunta equivocada es "¿cuánto de mi código está cubierto?". La correcta es "¿tengo el valor de refactorizar esto?".
Si la respuesta es no, la suite no está haciendo su trabajo, independientemente del porcentaje.
Pirámide, trofeo, y la pelea que no importa
La pirámide clásica: muchas pruebas unitarias en la base, algunas de integración en el medio, pocas de punta a punta arriba. La lógica es coste y velocidad: cuanto más alto, más lento y más frágil.
El trofeo, de Kent C. Dodds, redistribuye: pocas unitarias, muchas de integración, pocas de punta a punta, y análisis estático en la base. El argumento es que la mayoría de los bugs reales está en la interacción entre las piezas.
Quién tiene razón depende de lo que haga tu sistema.
Si tienes lógica de negocio densa (cálculo de precio, impuestos, reglas de elegibilidad), la pirámide sirve: hay mucho aislado y determinista que vale la pena fijar.
Si tu sistema es sobre todo pegamento entre base de datos, cola y API, el trofeo sirve mejor: probar cada clase aislada con mocks no demuestra casi nada.
La pregunta que prefiero a la pelea de formato
Si esta prueba pasa, ¿qué puedo afirmar exactamente sobre el sistema?
Si la respuesta es "que este método llama a aquel otro", la prueba vale poco.
Si es "que un pedido con cupón caducado se rechaza", vale mucho.
Los atributos de una buena prueba
Kent Beck los listó, y la lista es práctica:
Rápida. Una suite lenta deja de ejecutarse antes del commit, y el retorno pasa de segundos a media hora.
Determinista. Mismo código, mismo resultado, siempre.
Aislada. No depende de otra prueba ni del orden de ejecución.
Legible cuando falla. El mensaje debe decir qué se rompió sin que abras el depurador.
Sensible a bugs de verdad e insensible a la refactorización. Este es el más difícil y el más importante.
Los dobles, y por qué el mock es peligroso
Los nombres son específicos y la gente los intercambia:
Stub devuelve una respuesta preparada. Lo usas para controlar la entrada.
Mock verifica interacción: "ese método se llamó una vez con estos argumentos".
Fake es una implementación de verdad, simplificada: un repositorio en memoria.
Spy es el real, envuelto, registrando las llamadas.
Cuando verificas interacción, fijas el diseño interno del código.
Entonces refactorizas (extraes un método, cambias el orden de dos llamadas, sustituyes una dependencia por otra) y veinte pruebas se ponen en rojo sin que ningún comportamiento haya cambiado.
Eso le enseña al equipo que las pruebas son un obstáculo. Y se lo enseña bien, porque en ese caso lo son.
La regla: prueba comportamiento observable, no implementación. Entra esto, sale aquello. Usa mocks solo en el borde que no controlas: la pasarela de pago, el envío de email, el reloj.
Ante la duda entre mock y fake, prefiere fake. Un repositorio en memoria de treinta líneas resuelve más y se rompe menos.
- Stubcontrola la entradaDevuelve una respuesta preparada. No verifica nada, solo alimenta el caso.
- Fakela opción por defectoImplementación de verdad, simplificada. Un repositorio en memoria de treinta líneas.
- Spyel real, observadoEl objeto de verdad, envuelto, registrando las llamadas.
- Mockfija el diseñoVerifica interacción. Refactorizar deja veinte pruebas en rojo sin que nada haya cambiado.
Los síntomas de una prueba frágil
Si reconoces tres de estos, tu problema no es falta de pruebas, sino exceso del tipo equivocado:
→ se rompe cuando renombras un método privado
→ depende del orden de ejecución
→ usa mocks encadenados en tres niveles
→ verifica el formato de una cadena en vez del dato
→ tiene más líneas de preparación que de verificación
→ nadie en el equipo sabe explicar qué garantiza
Cobertura: mapa, no meta
La cobertura mide lo que se ejecutó, nunca lo que se verificó. Una prueba sin ningún assert da cien por cien en la línea que ejecuta.
Como meta, se vuelve una farsa. Ley de Goodhart: cuando la medida se convierte en meta, deja de ser una buena medida. Con una meta del ochenta por ciento, el equipo cubre getters y DTOs (barato e inútil) y deja fuera la regla difícil.
Cómo usarla bien:
→ Como mapa: mira lo que está en cero, sobre todo en código crítico.
→ En el diff: exige cobertura en el código nuevo del PR, no un número global. La curva sube sola, en el sitio correcto.
→ Cobertura de rama, no de línea.
Y si quieres la respuesta honesta sobre la calidad de tu suite, existe una prueba mejor que cualquier
informe: la prueba de mutación. La herramienta sabotea tu código (cambia > por >=, invierte un
if, elimina una llamada) y ejecuta la suite. Lo que siga en verde no se estaba verificando.
Puedes hacer la versión manual en dos minutos: invierte una comparación en el cálculo más crítico y ejecuta. Si pasa, el silencio responde.
Una prueba inestable mata la suite
Una prueba que pasa y falla con el mismo código es peor que una prueba que falta, porque le enseña al equipo a ignorar el rojo.
Con un dos por ciento de fallo aleatorio en una suite de quinientas pruebas, casi toda ejecución tiene un rojo. En dos semanas, "ejecútalo otra vez" se convirtió en proceso.
Las causas: sleep fijo (cámbialo por espera con condición y plazo); dependencia del orden;
concurrencia real; recurso externo, incluidos el reloj, la aleatoriedad y la zona horaria.
La política: la inestable se convierte en un issue con dueño y plazo. No en un retry automático: el retry esconde exactamente el bug de concurrencia que la prueba estaba, correctamente, encontrando.
Cómo encontrar las tuyas: ejecuta la suite veinte veces sobre el mismo commit, de noche. Tres o cuatro pruebas suelen ser responsables del ochenta por ciento de los rojos falsos.
TDD, sin religión
El ciclo es rojo, verde, refactoriza. La ganancia principal no es la prueba que queda: es que te obliga a usar tu propia interfaz antes de implementarla.
Dónde brilla: reglas de negocio con entrada y salida claras; corrección de bugs.
Dónde estorba: exploración, cuando todavía no conoces el diseño; integración con un sistema de terceros que aún no entiendes.
Y el uso menos controvertido y más rentable, que le recomendaría a cualquier equipo, incluso a los que no hacen TDD:
Todo bug corregido entra con una prueba que falla antes del arreglo.
Cuesta quince minutos, demuestra que entendiste la causa, y garantiza que ese bug no vuelve. En tres meses tienes una suite de regresión construida a partir de los errores que tu sistema realmente comete, mucho más valiosa que una construida a partir de lo que alguien imaginó que podría salir mal.
El pipeline
Cerrando con la estructura que sostiene todo esto: etapas de la más rápida a la más lenta. Verificación estática en segundos. Unitarias en hasta cinco minutos. Integración con base de datos en contenedor en hasta quince. Punta a punta y deploy al final.
Retorno en menos de diez minutos hasta la tercera etapa. Por encima de eso, la gente deja de ejecutarlo antes del commit.
Y la regla del tronco verde: main roto es una emergencia de equipo. O se revierte en minutos, o alguien para y lo arregla. Nadie construye sobre una base rota.
Lee esto después
- IngenieríaPaso 21Datos y analytics: del OLTP al lakehouseDos preguntas aparecen en toda empresa: por qué el informe tumbó producción, y por qué el número de mi panel es distinto del tuyo. Las dos tienen la misma causa raíz.Leer artículo
- IngenieríaPaso 20Legado: caracterizar, coser y estrangularTres técnicas que permiten tocar con seguridad un sistema que no escribiste, no entiendes, y no puedes parar.Leer artículo
- IngenieríaPaso 18OAuth2, OIDC y JWT sin misterioLas tres siglas más confundidas de la autenticación, qué resuelve cada una, y los errores que aparecen en casi todo el código.Leer artículo