Curso exprés · No. 21

Un test no es más que código que comprueba tu código, automáticamente. La gente cree que su trabajo es probar que el código de hoy funciona — pero la verdadera recompensa llega después: los tests son lo que te deja cambiar, refactorizar y añadir funciones mañana sin romper lo que ya funcionaba. Aprende los tipos y el vocabulario, y testear deja de ser una carga para convertirse en lo que te permite ir rápido.

Solo lo esencial · Una imagen por idea · Aprende las palabras

§ 01

Casi todo el mundo malinterpreta para qué sirven los tests. Parecen una forma de probar que el código funciona; su verdadero valor es algo más profundo y más práctico — y verlo cambia cómo los escribes.

Un test es código que comprueba código

Una segunda persona que rehace tus cuentas en una calculadora y marca las que no cuadran — automática, incansable e idéntica cada vez.

Un test es un trozo pequeño de código que ejecuta tu código con una entrada conocida y comprueba que el resultado es el que esperas. «Dado 2 y 3, ¿add devuelve 5?» Si sí, el test passes; si no, fails y te lo dice. Esa es toda la mecánica. Lo potente es que es automático y repetible — puedes ejecutar miles de estas comprobaciones en segundos, cada vez que cambias algo, sin que un humano vuelva a verificar a mano.

El verdadero sentido es cambiar sin miedo

El arnés de seguridad de un escalador: no hace la escalada por ti, pero te deja intentar un movimiento audaz porque un resbalón no será fatal. Escalas con más fuerza porque, si caes, te sujeta.

Aquí está el malentendido. Los tests no van sobre todo de probar que el código de hoy es correcto — van sobre mañana. Cuando cambias código, lo refactorizas o añades una función, un buen conjunto de tests te dice al instante si rompiste algo que antes funcionaba. Esa red de seguridad es lo que te deja cambiar una base de código grande con confianza en lugar de tener miedo de tocar nada. Los tests son cómo sigues yendo rápido sin que las cosas se rompan en silencio a tu espalda.

Cazan regressions automáticamente

Una hilera de hilos trampa por toda una casa: en el momento en que algo perturba una habitación que ni siquiera mirabas, salta una alarma — no tienes que patrullar cada habitación tú mismo.

Una regression es cuando un cambio rompe algo que antes funcionaba — a menudo en un sitio lejano a lo que tocaste. Sin tests, las regressions se esconden hasta que un usuario las encuentra. Con tests, en el momento en que tu cambio rompe un comportamiento viejo, un test se pone en rojo y lo señala, justo ahí. Esta es la recompensa del día a día: haces un cambio, ejecutas los tests y sabes al instante si también rompiste algo en otra parte. El conjunto vigila toda la casa mientras tú trabajas en una habitación.

Un test comprueba el código automáticamente. Su verdadero valor no es una prueba puntual — es la libertad de cambiar mañana sin romper en silencio lo que funciona hoy.

§ 02

El cimiento del testing es el unit test: una comprobación rápida y enfocada de una pieza pequeña de código por sí sola. La mayoría de tus tests serán estos, y con razón.

Testea una pieza pequeña en aislamiento

Comprobar que una sola pieza de Lego tiene la forma y el tamaño correctos antes de construir con ella — no montar el castillo entero para descubrir que una pieza estaba mal.

Un unit test comprueba una unidad pequeña — normalmente una sola función — en aislamiento del resto del sistema. Le das una entrada, compruebas su salida, nada más involucrado. Como la pieza es pequeña e independiente, cuando el test falla sabes exactamente qué se rompió y dónde. Los unit tests son el microscopio del testing: estrechos, precisos y apuntados a una sola cosa a la vez.

Rápidos y muchos es la idea entera

Un corrector ortográfico que escanea el documento entero en un parpadeo — tan rápido que lo ejecutas constantemente, no una revisión lenta que haces una vez al mes.

Como cada unit test es diminuto y no toca nada externo, corre en milisegundos — así que puedes tener miles y ejecutarlos todos en cada cambio, constantemente. Esa velocidad es el punto: un conjunto de tests que puedes ejecutar en segundos se ejecuta todo el tiempo, cazando roturas en el instante en que ocurren. Un conjunto lento se salta, y un conjunto que se salta no protege nada. Rápidos y abundantes es lo que hace de los unit tests el caballo de batalla.

Arrange, act, assert

Un experimento sencillo: prepara las condiciones, realiza la única acción y luego comprueba el resultado. Montaje claro, un movimiento, una verificación.

Un buen unit test tiene tres pasos sencillos, a menudo llamados arrange, act, assert: prepara las entradas, llama una vez a lo que estás testeando y luego haz un assert de que el resultado es el que esperabas. Una assertion es la comprobación en sí — «la respuesta debe ser igual a 5». Mantener los tests con esta forma limpia los hace legibles y hace obvio un fallo. Un test difícil de leer es uno en el que nadie confiará ni mantendrá.

Un unit test comprueba una pieza pequeña en aislamiento, en milisegundos — así que ejecutas miles constantemente. Arrange la entrada, act una vez, assert el resultado.

§ 03

Los unit tests comprueban piezas solas, pero el software son piezas trabajando juntas. Dos tipos de test más amplios cubren eso — y el equilibrio justo entre los tres tiene una forma que vale la pena conocer.

Los integration tests comprueban piezas trabajando juntas

Después de comprobar cada pieza de Lego, encajas unas cuantas para asegurarte de que de verdad conectan — las piezas estaban bien solas, pero ¿encajan?

Un integration test comprueba que varias partes funcionan correctamente juntas — tu código hablando con la base de datos, dos servicios intercambiando datos. Las unidades pueden ser cada una perfecta en aislamiento y aun así no llegar a conectar: un formato que no cuadra, una suposición errónea sobre el otro lado. Los integration tests cazan exactamente esas costuras. Son más lentos que los unit tests porque involucran más partes reales en movimiento, pero verifican las uniones que los unit tests, por diseño, se saltan.

Los end-to-end tests usan el sistema entero como un usuario

Un ensayo general de la obra completa, de principio a fin, en el escenario real — no comprobar diálogos o atrezo por separado, sino la función entera tal como la verá el público.

Un end-to-end test (e2e) maneja el sistema entero como lo haría un usuario real — pulsa el botón, rellena el formulario, comprueba que pasó lo correcto a lo largo de toda la pila. Es el test más realista y la confirmación más valiosa de que el producto de verdad funciona. Pero también es el más lento y frágil: muchas partes deben estar todas corriendo, y un pequeño cambio de UI puede romperlo. Usas los e2e tests para los pocos recorridos críticos, no para todo.

La test pyramid los equilibra

Una pirámide se sostiene porque su base es ancha y su cima estrecha — vuélcala sobre su punta y se derrumba. La forma en sí es la estabilidad.

La test pyramid es la regla práctica para el equilibrio: muchos unit tests rápidos en la base, menos integration tests en el medio y un puñado de end-to-end tests lentos en la cima. Esta forma te da una cobertura amplia y rápida de forma barata, con justo las suficientes comprobaciones realistas encima. El antipatrón — montones de e2e tests lentos y pocos unit tests — es el «cono de helado», y es lento, flaky y doloroso. Apunta a la pirámide, no al cono.

Los unit tests comprueban piezas solas, los integration tests las comprueban juntas, los e2e tests comprueban el sistema entero como un usuario. Mantén la pirámide: muchos rápidos, pocos lentos.

§ 04

Para testear una pieza en aislamiento, a menudo tienes que hacer de suplente de las cosas reales de las que depende. Eso es lo que son los mocks y los stubs — y son tan fáciles de usar mal como útiles.

Reemplaza una dependencia real con un fake

Un simulador de vuelo en lugar de un avión real: instrumentos y vistas falsos te dejan practicar la habilidad de forma segura, sin el coste y el riesgo de volar de verdad.

Para testear una pieza en aislamiento, reemplazas sus dependencias reales — una base de datos, un servicio de pagos, un emisor de correo — con fakes que hacen de suplentes. Esto te deja testear tu código sin la cosa real lenta, costosa o impredecible. No quieres que un unit test cobre de verdad una tarjeta o envíe un correo; metes en su lugar un fake que finge hacerlo, así el test sigue siendo rápido, fiable y autocontenido.

El stub da respuestas enlatadas; el mock comprueba la llamada

Un stub es una figura de cartón que solo dice una frase cuando toca; un mock es un actor que además te reporta exactamente qué le dijiste y cómo.

Dos sabores de fake se confunden. Un stub simplemente devuelve una respuesta fija y enlatada — «finge que la base de datos devuelve este usuario» — para que puedas testear cómo maneja tu código esa entrada. Un mock además verifica la interacción — «comprueba que mi código llamó a sendEmail exactamente una vez, con esta dirección». Un stub alimenta tu código; un mock vigila cómo se comporta tu código hacia él. Echa mano de un stub para suministrar datos, de un mock para afirmar que ocurrió un efecto secundario.

Abusar de los mocks crea tests que mienten

Ensayar una obra entera con suplentes de cartón para todos los demás actores — puedes clavar tus líneas y aun así no tener ni idea de si el reparto real va a funcionar de verdad junto.

Los fakes son potentes pero peligrosos en exceso. Si haces mock de todo, tu test pasa contra tus suposiciones sobre cómo se comportan las otras partes — no sobre cómo lo hacen de verdad. El test se pone en verde mientras la integración real está rota. Así que haz mock de las dependencias lentas, externas o impredecibles, pero no falsees la propia cosa que intentas verificar. Un test construido enteramente sobre mocks puede pasar mientras el sistema real falla — la clase de verde más peligrosa.

Los mocks y los stubs hacen de suplentes de dependencias reales para que puedas testear en aislamiento. Un stub suministra datos; un mock verifica una llamada. Abusa del mock, y el test pasa mientras la realidad se rompe.

§ 05

Más allá de los tipos de test, está el oficio de escribir buenos tests — y una disciplina, el test-driven development, que invierte el orden habitual y cambia cómo diseñas el código.

TDD: escribe el test primero

Dibujar la diana antes de disparar, no después — para que apuntes a un objetivo definido en lugar de pintar el centro alrededor de donde la flecha cayó por casualidad.

El test-driven development (TDD) invierte el orden habitual: escribes el test antes que el código. El ciclo es red, green, refactor — escribe un test que falle para lo que quieres (red), escribe justo el código suficiente para que pase (green), luego limpia el código con el test protegiéndote (refactor). Escribir el test primero te obliga a definir exactamente qué significa el éxito antes de construir, y garantiza que el código sea testeable, porque nació de un test.

Testea el behaviour, no la implementación

Juzgar a un chef por si el plato sabe bien, no por con qué mano sostuvo el cuchillo — importa el resultado, no los detalles privados de cómo llegó ahí.

El hábito más importante para tests duraderos: comprueba qué hace el código, no cómo lo hace. Testea que getDiscount devuelve el precio correcto, no que llamó a tres ayudantes internos concretos en orden. Los tests atados al resultado sobreviven a la refactorización — puedes reescribir las tripas y siguen pasando. Los tests atados a la implementación se rompen cada vez que ordenas el código, lo que entrena a la gente a desconfiar de ellos y borrarlos. Testea el behaviour, y el test sigue siendo útil.

Los buenos tests son documentación

Un conjunto de tests bien escrito se lee como una lista de promesas: «dado esto, hace aquello» — más claro que cualquier comentario sobre cómo se supone que se comporta el código.

Un conjunto de tests claro sirve también como documentación viva. Cada test enuncia una promesa — «dado un carrito vacío, el total es cero» — y, como los tests se ejecutan, esa documentación nunca puede quedarse obsoleta como les pasa a los comentarios; si el comportamiento cambia, el test se rompe. Así que escribe los tests para ser leídos: nombres claros, un comportamiento cada uno, un arrange-act-assert obvio. Un recién llegado debería poder aprender qué hace el código leyendo sus tests. Esa legibilidad vale tanto como la comprobación.

Escribe el test primero (red-green-refactor), testea el behaviour no la implementación, y hazlo legible — para que los tests sobrevivan a los refactors y sirvan a la vez de documentación que no puede quedarse obsoleta.

§ 06

Dos números y una molestia determinan si un conjunto de tests es de verdad fiable. Malinterpreta el coverage o tolera la flakiness, y un conjunto en verde deja de significar nada.

El coverage mide qué se testea, no cómo de bien

Un mapa que muestra por qué calles pasó una patrulla — útil para detectar los barrios nunca visitados, pero pasar por delante de una casa no significa que comprobaras las cerraduras.

El coverage es el porcentaje de tu código que se ejecuta durante los tests. Es una guía útil para encontrar lo que está completamente sin testear — las calles nunca patrulladas. Pero un coverage alto no significa buenos tests: el código puede ser ejecutado por un test que no afirma nada significativo, sacando un 100% mientras verifica poco. Usa el coverage para encontrar puntos ciegos, no como prueba de calidad. Que una línea sea tocada no es lo mismo que una línea sea comprobada.

No dejes que el coverage se convierta en el objetivo

Pagar a los trabajadores por kilómetro de carretera pintada, y verlos pintar líneas largas e inútiles en solares vacíos para alcanzar el número — la métrica subió, las carreteras no se volvieron más seguras.

Cuando un número se convierte en objetivo, la gente optimiza el número en lugar de la cosa que medía — la ley de Goodhart otra vez. Exige «100% de coverage» y obtienes tests escritos para tocar líneas, no para cazar bugs: tests huecos que no afirman nada, manipulando la métrica. El coverage es una linterna para encontrar huecos, no un trofeo que maximizar. Persigue confianza real — ¿cazan los tests de verdad las roturas? — y deja que el coverage sea una pista entre varias, nunca el objetivo en sí.

Un flaky test es peor que ningún test

Una alarma de incendios que salta al azar sin motivo: en una semana todos la ignoran por completo, así que cuando hay un incendio real, nadie se mueve.

Un test flaky es uno que pasa y falla al azar sin que el código cambie — normalmente por timing, ordenación o una dependencia oculta. Los flaky tests son veneno: cuando un resultado en rojo podría ser solo ruido, la gente deja de confiar en cualquier rojo, y un fallo real pasa colado como «probablemente solo flaky». Un test debe ser deterministic — mismo código, mismo resultado, cada vez. Arregla los flaky tests o bórralos; un conjunto en el que no confías no protege nada.

El coverage muestra qué se testea, no cómo de bien — no lo conviertas en el objetivo. Y un flaky test es peor que ninguno: una vez que el rojo puede significar ruido, todo rojo se ignora.

§ 07

Testear es criterio, no ritual. La habilidad es testear las cosas que importan, en las proporciones justas, e integrarlo en cómo despliegas — para que el conjunto se gane la confianza en lugar de convertirse en trabajo de relleno.

Testea las partes arriesgadas, no todo

Compruebas tres veces el paracaídas y los frenos; no sometes a estrés el portavasos. El esfuerzo va donde el fallo de verdad duele.

No todo el código merece el mismo testing. Concentra el esfuerzo donde un bug sería costoso o probable: la lógica de negocio central, el dinero, la seguridad, los casos límite peliagudos, las partes que cambian a menudo. El código trivial y de bajo riesgo puede llevar menos. Perseguir tests para todo malgasta esfuerzo y crea un conjunto tan grande y lento que nadie lo ejecuta. Apunta a tests que cacen los fallos que de verdad lamentarías, no a un número — el coverage de lo que importa le gana al coverage de todo.

Ejecuta los tests automáticamente, en cada cambio

Un torno que no se abre a menos que tu billete sea válido — la comprobación está integrada en la barrera, no dejada a si alguien se acuerda de mirar.

Los tests solo te protegen si de verdad se ejecutan. Intégralos en tu pipeline para que corran automáticamente en cada cambio, y bloquea el merge si fallan — el CI gate del curso de despliegue. Esto es lo que convierte los tests de algo que quizá ejecutes en una garantía de que nada roto se mergea. «Tests o no se desplegó» es el estándar que vale la pena sostener: un cambio sin testear yendo a producción es una apuesta que no tenías por qué hacer.

Antes de confiar en un conjunto de tests
  • ¿Te deja cambiar con confianza — cazaría una regression mañana? - ¿Tiene forma de pirámide — muchos unit, algunos integration, unos pocos e2e? - ¿Comprueban los tests el behaviour, no la implementación interna que se rompe al refactorizar? - ¿Se usan los mocks para deps externas reales, no para falsear la cosa bajo test? - ¿Es rápido y deterministic — sin flaky tests erosionando la confianza? - ¿Se ejecuta en CI en cada cambio, bloqueando los merges rotos?
Las palabras que ahora dominas
  • test / pass / fail / assertion — una comprobación sobre el código, sus resultados y la comprobación en sí. - regression — un cambio que rompe algo que antes funcionaba. - unit / integration / end-to-end (e2e) — una pieza, piezas juntas, el sistema entero. - test pyramid — muchos unit tests rápidos, menos integration, unos pocos e2e. - mock / stub — un fake que verifica una llamada, un fake que suministra datos. - TDD / red-green-refactor — escribir el test primero, en un ciclo. - coverage / flaky / deterministic — qué se testea, la trampa del fallo al azar y la cura.
Señales de que testeas bien
  • Cambias el código con confianza, fiándote del conjunto para cazar lo que rompiste. - Tus tests forman una pirámide — rápidos en la base, unos pocos realistas en la cima. - Los tests comprueban el behaviour y sobreviven a los refactors en lugar de romperse en cada limpieza. - El conjunto es rápido y deterministic, y arreglas o borras los tests flaky. - Los tests se ejecutan en CI en cada cambio, y tratas el coverage como una pista, no como un objetivo.

El buen testing es criterio: testea las partes arriesgadas, mantén la pirámide, comprueba el behaviour no la implementación, y ejecútalo todo automáticamente — para que un conjunto en verde signifique de verdad seguro para cambiar.

Fin del curso exprés · 7 capítulos · aprende las palabras

Ahora viene la práctica: coge una función importante y escríbele unos cuantos unit tests — arrange, act, assert — luego rompe la función a propósito y observa cómo un test se pone en rojo y te dice exactamente qué. Después añade un test para el siguiente bug que encuentres, para que el conjunto crezca a lo largo de los fallos que de verdad ocurren. Pero mantén una idea por encima del resto: los tests no van sobre probar que el código de hoy funciona. Son el arnés de seguridad que te deja cambiar mañana sin miedo — y esa libertad de cambiar con confianza es lo que permite que el buen software siga creciendo en lugar de calcificarse poco a poco.