fedorthinks

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.

19 conceptos · un diagrama para cada uno · chuleta al final

§ 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.

El mapa: 19 conceptos en siete capítulos

Un mapa de metro: primero ves todas las líneas de golpe, luego recorres estación por estación.

Cada concepto del curso está en uno de siete capítulos. Primero, por qué existen los tests. Luego, los tipos de test y cómo equilibrarlos. Luego, los falsos que necesitas para aislar un trozo de código. Después: el oficio de un buen test, confiar en el conjunto de tests y ponerlo todo a trabajar.

1 · Why we testAutomated testRegression2 · Unit testsUnit testArrange–Act–Assert3 · PyramidIntegration testE2E testTest pyramid4 · FakesTest doubleStubMockOver-mocking5 · CraftTDDTest behaviourLiving docs6 · TrustCode coverageGoodhart's lawFlaky test7 · PracticeRisk-based testingCI gateCheat sheetall terms, at the end
Siete capítulos, 19 conceptos. Al final te espera una chuleta con todos ellos.

Cómo leer este curso

Un pasaporte: los mismos campos para todos, así es fácil compararlos.

Cada concepto recibe el mismo trato: su nombre en inglés (recuérdalo — es el término que encontrarás en la documentación y en las entrevistas), una imagen de la vida real, un diagrama y una tarjeta con campos: problema, idea, ejemplo, coste, cuándo usarlo y conceptos relacionados. Una idea recorre todo el curso: los tests son un seguro para los cambios futuros, no un certificado de que el código funcionó una vez.

Automated Test — 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.

Inputadd(2, 3)Code under testadd()Assertionexpect 5PASSgot 5FAILgot 6automatic · repeatable · thousands of checks per run
Un test da al código una entrada conocida y compara el resultado con lo esperado: coincide — pasa, no coincide — falla.
ConceptAutomated Test· test, autotest
Problema

Comprobar a mano es lento y aburrido — así que tras cada cambio nadie vuelve a comprobarlo todo.

Idea

Escribe la comprobación como código: entrada conocida → llamada → comparar con lo esperado. Resultado: pasa o falla.

Ejemplo

«add(2, 3) debe devolver 5» — una línea, se ejecuta en milisegundos.

Coste

Los tests también son código: hay que escribirlos, leerlos y mantenerlos.

Cuándo usarlo
Para cualquier código que vayas a seguir cambiando.
Relaciones

Caza Regressions; viene como Unit, Integration y E2E; se ejecuta en el CI Gate.

Safety Net — lo importante 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 (test suite) 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.

Changerefactor · featureTest suiteruns in secondsgreen ✓old behaviour okred ✗broke old codeShip itwith confidenceFix it nowbefore users see
Todo cambio pasa por el conjunto de tests: verde — lo publicas con confianza; rojo — lo arreglas ahora, no cuando se queje un usuario.

Regression — cuando un cambio rompe lo que funcionaba

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.

CodebaseCartyou changed thisPricingCheckoutInvoicesbroke silentlyinvoice test ✗a tripwire in a room you weren't looking at
Cambiaste el carrito y se rompieron las facturas — a tres módulos de distancia. El test de facturas se pone rojo enseguida.
TermRegression· regression bug
Qué es
Un cambio rompe un comportamiento que antes funcionaba.
Trampa

A menudo se rompe lejos de donde editaste, en una parte distante del sistema.

Ejemplo

Cambiaste el total del carrito — y las facturas empezaron a mostrar un importe incorrecto.

Cómo cazarla

Un conjunto de tests sobre el comportamiento antiguo, ejecutado en cada cambio.

Consejo
¿Encontraste un bug? Añade un test para que nunca vuelva.
Relaciones

La caza un Automated Test; la vigila automáticamente el CI Gate.

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.

Unit Test — 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.

isolationUnitone functioninputexpected?not touched:DatabaseNetworkDisk
Un unit test: entrada → una función → comprobar la salida. La base de datos, la red y el disco se quedan fuera.
ConceptUnit Test· test de módulo
Problema

Cuando falla una comprobación grande, no sabes cuál de cien partes tiene la culpa.

Idea

Comprueba una función cada vez, aislada de la base de datos, la red y todo lo demás.

Ejemplo

calculateTax(100) devuelve 20 — sin base de datos, sin HTTP, en milisegundos.

Coste

No ve cómo funcionan juntas las piezas — de eso se encargan otros tests.

Cuándo usarlo

Para la mayoría de tus tests: lógica de negocio, cálculos, reglas, casos límite.

Relaciones

La base de la Test Pyramid; se aísla con Test Doubles; tiene la forma Arrange–Act–Assert.

Rápidos y muchos — esa es toda la idea

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.

time per testunitmillisecondsintegrationslowere2eslowestfast suite → run on every change → breakage caught instantly
Cuanto más bajo el nivel, más rápido el test. Un conjunto rápido se ejecuta en cada edición — por eso caza las roturas.

Arrange–Act–Assert — preparar, actuar, comprobar

Un experimento sencillo: preparas las condiciones, haces la única acción y luego compruebas el resultado. Preparación clara, un movimiento, una verificación.

Un buen unit test tiene tres pasos simples — el patrón Arrange–Act–Assert (AAA): prepara las entradas, llama una sola vez a lo que pruebas y luego afirma (assert) que el resultado es el esperado. Una assertion es la comprobación en sí — «la respuesta debe ser igual a 5.» Mantener los tests en esta forma limpia los hace legibles y hace evidente un fallo. Un test difícil de leer es uno en el que nadie confiará ni querrá mantener.

1 · Arrangecart = new Cart()cart.add(item, 2)2 · Acttotal = cart.total()3 · Assertexpect(total) .toBe(20)set up · do one thing · check the resultassertion = the check itself
Los tres pasos de un test: Arrange arma el carrito, Act calcula el total, Assert lo compara con lo esperado.
PatternArrange–Act–Assert· AAA
Problema

Un test enredado: preparación, llamadas y comprobaciones mezcladas — no se ve qué verifica.

Idea

Tres bloques en orden: preparación → una acción → comprobación (assertion).

Ejemplo

Arrange: un carrito con dos artículos. Act: cart.total(). Assert: el total es igual a 20.

Coste
Casi ninguno — solo la disciplina de una acción por test.
Cuándo usarlo
Siempre, en todo test — unit, integration o end-to-end.
Relaciones

Hace legible un test — y por tanto Living Documentation; la columna vertebral de un Unit Test.

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

§ 03

Los unit tests comprueban las piezas por separado, 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 merece conocerse.

Integration Test — piezas trabajando juntas

Después de comprobar cada pieza de Lego, encajas unas cuantas para asegurarte de que de verdad se conectan — las piezas estaban bien por separado, 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. Cada unidad puede ser perfecta por separado y aun así no conectar: un formato que no coincide, una suposición equivocada sobre el otro lado. Los integration tests cazan justo esas costuras (seams). Son más lentos que los unit tests porque intervienen más piezas reales en movimiento, pero verifican las uniones que los unit tests, por diseño, se saltan.

seamYour codeDatabasequeryrowsService AService BJSONreplybugs hide in the joins: format mismatch, wrong assumptions
Un integration test comprueba la costura: la consulta a la base de datos y su respuesta, el JSON entre servicios. Los bugs se esconden en las uniones.
ConceptIntegration Test· comprobación de integración
Problema
Cada parte pasa sus unit tests, pero juntas no funcionan.
Idea
Ejecuta varias partes reales juntas y comprueba la unión entre ellas.
Ejemplo

Un repositorio escribe un usuario en una base de datos de prueba real y lo lee de vuelta.

Coste

Más lento que los unit tests; necesita un entorno montado (base de datos, servicios).

Cuándo usarlo

Para las costuras: código ↔ base de datos, servicio ↔ servicio, tu código ↔ una API de terceros.

Relaciones

La capa intermedia de la Test Pyramid; caza lo que oculta el Over-mocking.

End-to-End Test — todo el sistema, como un usuario

Un ensayo general de toda la obra, de principio a fin, en el escenario real — no solo las frases o el atrezo, sino la función entera tal como la verá el público.

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

the whole stack, really runningUserclicks, typesUIbrowserAPIserverDatabaseOrder placed?assertmost realistic · slowest · most fragileuse it for the few critical journeys
Un test E2E va del usuario por la interfaz, la API y la base de datos — y comprueba el resultado: ¿se hizo el pedido?
ConceptEnd-to-End Test· E2E
Problema

Cada parte está probada, pero nadie sabe si el producto funciona como un todo.

Idea

Recorre el camino de un usuario por toda la pila real y comprueba el resultado.

Ejemplo

Un robot de navegador: inicia sesión, añade un artículo al carrito, paga, ve «pedido realizado».

Coste

El más lento y el más frágil; se rompe con pequeños cambios de interfaz.

Cuándo usarlo

Para pocos recorridos críticos: inicio de sesión, pago, el flujo principal.

Relaciones

La punta de la Test Pyramid; con demasiados te sale el cono de helado.

Test Pyramid — el equilibrio justo

Una pirámide se sostiene porque su base es ancha y su punta estrecha — ponla del revés sobre la punta y se cae. La forma misma es la estabilidad.

La test pyramid es la regla práctica del 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 arriba. Esta forma te da una cobertura amplia y rápida a bajo coste, con justo los checks realistas necesarios en la cima. El antipatrón — muchos e2e tests lentos y pocos unit tests — es el cono de helado (ice-cream cone), y es lento, inestable y doloroso. Apunta a la pirámide, no al cono.

E2EIntegrationsomeUnitmany, fastTest pyramid ✓E2Emany, slowIntegrationunit (few)Ice-cream cone ✗
A la izquierda, la pirámide: muchos unit tests, menos integration, unos pocos e2e. A la derecha, el cono de helado — la misma pirámide al revés.
PatternTest Pyramid· pirámide de testing
Problema

Hay muchos tests, pero el conjunto es lento e inestable — así que la gente deja de ejecutarlo.

Idea
Muchos tests baratos y rápidos abajo, pocos caros y lentos arriba.
Ejemplo

Miles de unit tests, cientos de integration tests, una docena de e2e para los flujos principales.

Coste
Hay que decidir a conciencia qué nivel comprueba cada cosa.
Antipatrón
Ice-cream cone — la pirámide al revés: muchos e2e, pocos unit.
Relaciones

Las capas son Unit, Integration, E2E; los e2e de más engendran Flaky Tests.

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

§ 04

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

Test Double — un falso en lugar de la dependencia real

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

Para probar una pieza en aislamiento, sustituyes sus dependencias reales — una base de datos, un servicio de pagos, un envío de correo — por falsos que hacen su papel. El nombre general de cualquiera de esos falsos es test double, como un doble de acción en una película. Esto te permite probar tu código sin la cosa real, que es lenta, cara o impredecible. No quieres que un unit test cobre una tarjeta de verdad o envíe un correo; metes un falso que finge hacerlo, y así el test sigue siendo rápido, fiable y autocontenido.

test doublereal thingCode under testFake DBFake paymentsFake emailPostgresslowStripecosts moneySMTPsends for real
El código bajo prueba habla con falsos, no con el Postgres, el Stripe y el servidor de correo reales — esos son lentos, cuestan dinero o envían cosas de verdad.
ConceptTest Double· falso
Problema

Las dependencias reales son lentas, cuestan dinero o se comportan de forma impredecible — los tests que las usan son frágiles.

Idea

Sustituye por un objeto que se comporta como la dependencia, pero solo finge.

Ejemplo
En lugar de Stripe, un falso que siempre responde «pago realizado».
Coste
Un falso puede mentir: la cosa real puede comportarse de otra manera.
Cuándo usarlo

Para cosas externas, lentas o aleatorias: red, pagos, correo, hora.

Relaciones

Dos tipos principales — Stub y Mock; da aislamiento a un Unit Test; si te pasas, es Over-mocking.

Stub vs Mock — respuesta enlatada vs llamada comprobada

Un stub es un recortable de cartón que dice una frase cuando le toca; un mock es un actor que además te informa exactamente de lo que le dijiste y cómo.

Se confunden dos tipos de falso. Un stub simplemente devuelve una respuesta fija, enlatada — «finge que la base de datos devuelve este usuario» — para que pruebes cómo maneja tu código esa entrada. Un mock también verifica la interacción — «comprueba que mi código llamó a sendEmail exactamente una vez, con esta dirección.» Un stub alimenta a tu código; un mock vigila cómo se comporta tu código hacia él. Usa un stub para suministrar datos, un mock para afirmar que ocurrió un efecto secundario.

Stubsupplies dataStubgetUser()Your codereturns { name: 'Ann' }test checks your code's outputMockverifies the callYour codeMocksendEmail()expect(sendEmail) .calledOnceWith(addr)test checks the call happened
Un stub suministra datos, y el test comprueba la salida de tu código. Un mock registra la llamada, y el test comprueba la propia llamada.
TermStub· respuesta enlatada
Qué es
Un falso que devuelve una respuesta preparada.
Ejemplo
getUser() siempre devuelve { name: 'Ann' }.
Qué compruebas
La salida de tu código con esos datos.
Cuándo usarlo
Cuando tu código necesita recibir algo de una dependencia.
TermMock· registrador de llamadas
Qué es
Un falso que registra las llamadas y te deja verificarlas.
Ejemplo

Comprobar que sendEmail se llamó exactamente una vez, con la dirección correcta.

Qué compruebas

La propia llamada: si ocurrió, cuántas veces, con qué argumentos.

Cuándo usarlo

Cuando importa el efecto secundario: el correo salió, el pago se envió.

Over-mocking — tests que mienten

Ensayar una obra con recortables de cartón en lugar de todos los demás actores — puedes clavar tus frases y aun así no tener ni idea de si el reparto real funcionará junto.

Los falsos 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 contra cómo se comportan de verdad. El test se pone en verde mientras la integración real está rota. Ese es el antipatrón del over-mocking. Así que haz mock de las dependencias lentas, externas o impredecibles, pero no falsees justo lo que intentas verificar. Un test construido solo con mocks puede pasar mientras el sistema real falla — el verde más peligroso.

in the test: everything mockedCodeMock DBMock APIPASS ✓in production: the real partsCodeReal DBReal APIFAIL ✗green test, broken system — the most dangerous green
En el test, todo es mock — y está en verde. En producción, la base de datos y la API reales — y todo falla.
Anti-patternOver-mocking· demasiados mocks
Síntoma
El test está en verde, pero la misma función falla en producción.
Causa

Todo lo que la rodea es falso — el test solo comprueba tus suposiciones sobre otras partes.

Ejemplo

El mock de la base de datos siempre dice «ok», mientras la base de datos real rechaza la consulta por un campo equivocado.

Remedio

Haz mock solo de lo externo y lento; comprueba las costuras con un Integration Test.

Relaciones

Mal uso de los Test Doubles; erosiona la confianza igual que un Flaky Test.

Los mocks y los stubs sustituyen a las dependencias reales para que puedas probar en aislamiento. Un stub suministra datos; un mock verifica una llamada. Si abusas del mock, el test pasa mientras la realidad se rompe.

§ 05

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

TDD — escribe primero el test

Dibujar la diana antes de disparar, no después — así apuntas a un objetivo definido en lugar de pintar la diana alrededor de donde cayó la flecha.

El desarrollo guiado por tests (TDD, test-driven development) invierte el orden habitual: escribes el test antes que el código. El ciclo es rojo, verde, refactor — escribe un test que falla para lo que quieres (rojo), escribe el código justo para que pase (verde), luego limpia el código con el test vigilándote (refactor). Escribir primero el test 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.

Redwrite a failing testGreenjust enough codeRefactorclean up, stay greenrepeat
El ciclo TDD: rojo (el test falla) → verde (el mínimo código que pasa) → refactor (limpiar, los tests siguen en verde) → otra vez.
PracticeTest-Driven Development· TDD, red-green-refactor
Problema

Los tests se escriben «más tarde» — y el código sale difícil de testear.

Idea

Primero un test que falla, luego el mínimo código, luego una limpieza. Y vuelta a empezar.

Ejemplo

El test «carrito vacío → el total es 0» falla; escribes total(); se pone en verde; ordenas.

Coste

Poco familiar y más lento al principio; difícil cuando la propia tarea aún no está clara.

Cuándo usarlo

Para lógica con reglas claras: cálculos, validación, corrección de bugs.

Relaciones

Da código testeable y Living Documentation; el refactor se apoya en Test Behaviour.

Test Behaviour, Not Implementation — comprueba qué, no cómo

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

El hábito más importante para tests duraderos: comprueba qué hace el código, no cómo lo hace — su comportamiento, no su implementación. Prueba que getDiscount devuelve el precio correcto, no que llamó a tres ayudantes internos concretos en orden. Los tests atados al resultado sobreviven al refactor — 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 enseña a la gente a desconfiar de ellos y a borrarlos. Prueba el comportamiento, y el test sigue siendo útil.

getDiscount()helperhelperhelperinternals (free to change)inputcart, couponprice90✓ check the outcome✗ test coupled to internals — breaks on every refactor
Un buen test mira lo que entra en getDiscount y lo que sale. Un mal test se mete en los ayudantes internos — y se rompe en cada refactor.
PrincipleTest Behaviour, Not Implementation· black-box testing
Problema

Los tests se rompen en cada refactor, aunque el comportamiento no cambió.

Idea
Comprueba solo el resultado observable, a través de la interfaz pública.
Ejemplo

Comprueba que getDiscount devolvió 90 — no qué ayudantes llamó por dentro.

Coste

A veces un caso límite es más difícil de alcanzar solo con entrada y salida.

Cuándo usarlo
Siempre, por defecto.
Relaciones

Hace posible el refactor en TDD; los Mocks de más suelen atar los tests a la implementación.

Living Documentation — los tests como 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 debe comportarse el código.

Un conjunto de tests claro hace de 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 anticuada como los comentarios; si cambia el comportamiento, el test se rompe. Así que escribe los tests para que se lean: nombres claros, un comportamiento cada uno, un arrange-act-assert evidente. Un recién llegado debería poder aprender qué hace el código leyendo sus tests. Esa legibilidad vale tanto como la comprobación.

ConceptLiving Documentation· especificación ejecutable
Problema

Los comentarios y las wikis mienten: el código cambió, el texto se quedó viejo.

Idea

Los tests son promesas escritas como código. Rompes una promesa — el test se pone en rojo.

Ejemplo

Un test llamado «carrito vacío — el total es cero». Se lee como una especificación.

Coste
Tiempo para nombres claros y un comportamiento por test.
Cuándo usarlo
Siempre — sobre todo en código que leen los recién llegados.
Relaciones
Se apoya en Arrange–Act–Assert y Test Behaviour.

Escribe primero el test (red-green-refactor), prueba el comportamiento y no la implementación, y hazlo legible — para que los tests sobrevivan a los refactors y sirvan de documentación que no se queda obsoleta.

§ 06

Un número y una molestia deciden si un conjunto de tests es de verdad fiable. Si lees mal el coverage o toleras la inestabilidad, un conjunto en verde deja de significar nada.

Code Coverage — qué está probado, no lo bien que está

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

El code coverage es el porcentaje de tu código que se ejecuta durante los tests. Es una guía útil para encontrar lo que está totalmente sin probar — las calles que nadie patrulló. Pero un coverage alto no significa buenos tests: un test puede ejecutar el código sin afirmar nada significativo, sacando un 100% mientras verifica poco. Usa el coverage para encontrar puntos ciegos, no como prueba de calidad. Que una línea se toque no es lo mismo que que una línea se compruebe.

12345678executed + assertedexecuted, nothing assertednever executedcoverage: 6 of 8 lines (75%)really checked: 3 of 8a touched line is not a checked line
75% de coverage: 6 de 8 líneas se ejecutaron. Pero solo 3 las comprueba de verdad una assertion.
MetricCode Coverage· cobertura de tests
Qué es
La parte de las líneas (o ramas) que se ejecutaron durante los tests.
Sirve para
Mostrar puntos ciegos — código que ningún test ejecutó nunca.
Trampa

No mide la calidad: una línea puede ejecutarse sin que ninguna assertion la compruebe.

Ejemplo

80% de coverage, pero toda la lógica de descuentos está en el 20% sin cubrir — ahí está el riesgo.

Cuándo usarlo
Como linterna para los huecos, no como objetivo.
Relaciones

Se vuelve una trampa por la Goodhart's Law; no sustituye al Risk-based Testing.

Goodhart's Law — no dejes que el coverage sea el objetivo

Pagar a los obreros por milla de carretera pintada, y verlos pintar líneas largas e inútiles en descampados para llegar al número — la métrica subió, las carreteras no fueron más seguras.

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

Coveragea measurebecomes“100% or no merge”a targetHollow testsassert nothingthe number went up — the bugs stayedcoverage is a flashlight, not a trophy
Una medida se convierte en objetivo — y nacen los tests huecos: el número sube, los bugs se quedan.
LawGoodhart's Law· medida vs objetivo
Esencia

Cuando una medida se convierte en objetivo, deja de ser una buena medida.

En testing

Una regla «ningún merge sin 100% de coverage» cría tests sin assertions — solo por el número.

Ejemplo

Un test llama a la función y no comprueba nada — el coverage sube, la protección es cero.

Remedio

Pregunta «¿cazaría este test una rotura?» y trata el coverage como una pista.

Relaciones
La principal trampa del Code Coverage.

Flaky Test — peor que no tener 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 de verdad, nadie se mueve.

Un flaky test es uno que pasa y falla al azar sin que el código cambie — normalmente por tiempos, orden o una dependencia oculta. Los flaky tests son veneno: cuando un resultado en rojo puede ser solo ruido, la gente deja de confiar en cualquier rojo, y un fallo real se despacha con un «seguro que es solo flaky». Un test debe ser determinista — mismo código, mismo resultado, siempre. Arregla los flaky tests o bórralos; un conjunto en el que no confías no protege nada.

same code, nothing changed — six runsPASSrun 1FAILrun 2PASSrun 3PASSrun 4FAILrun 5PASSrun 6red might be noise → people stop trusting any reddeterministic: same code → same result, every time
El mismo código, seis ejecuciones — y dos fallos aleatorios. Después de eso, todo rojo se descarta como ruido.
TermFlaky Test· test inestable
Qué es
Un test que a veces pasa y a veces falla, sin cambios en el código.
Causas

Tiempos (sleeps, carreras), orden de los tests, una dependencia compartida oculta.

Ejemplo

Un test espera 100 ms a un servicio; en una máquina de CI ocupada a veces se rinde demasiado pronto.

Daño
Enseña al equipo a ignorar el rojo — y un fallo real se cuela.
Remedio

Haz el test determinista — o bórralo. Nunca convivas con un flaky test.

Relaciones

Se siente más en casa en E2E; destroza la confianza en el CI Gate.

El coverage muestra qué está probado, no lo bien que está — no lo conviertas en el objetivo. Y un flaky test es peor que ninguno: cuando el rojo puede ser ruido, todo rojo se ignora.

§ 07

Testear es criterio, no ritual. La habilidad está en probar lo que importa, en las proporciones justas, y en engancharlo a cómo publicas — para que el conjunto gane confianza en lugar de convertirse en trabajo de relleno.

Risk-based Testing — prueba las partes de riesgo, no todo

Compruebas tres veces el paracaídas y los frenos; no le haces una prueba de resistencia al portavasos. El esfuerzo va donde el fallo de verdad duele.

No todo el código merece el mismo testing. El risk-based testing significa concentrar el esfuerzo donde un bug sería costoso o probable: la lógica de negocio central, el dinero, la seguridad, los casos límite delicados, las partes que cambian a menudo. El código trivial y de bajo riesgo puede recibir 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 — cubrir lo que importa vale más que cubrirlo todo.

Test wellrare but costlyTest hardestmoney · security · core logicTest lightlytrivial, low-risk codeTest someedge cases · frequent changeshigh costlow costunlikely to breaklikely to breakeffort goes where failure actually hurts
Cuanto más costoso el fallo y más probable la rotura, más tests. El dinero, la seguridad y la lógica central están en la esquina superior derecha.
PracticeRisk-based Testing· testing por riesgo
Problema

El tiempo para testear es escaso, y intentar cubrirlo todo lo reparte demasiado fino.

Idea
Más tests donde el fallo es costoso o probable; menos en lo trivial.
Ejemplo

Las cuentas de pagos y los permisos de acceso — muy probados; los getters y el maquetado simple — lo mínimo.

Coste
Tienes que juzgar el riesgo con honestidad, no probar lo que sea fácil.
Cuándo usarlo
Cada vez que decides dónde gastar el tiempo de testing.
Relaciones

Sustituye la persecución del Code Coverage; más denso como Unit Tests sobre la lógica.

CI Gate — tests en cada cambio, automáticamente

Un torniquete que no se abre si tu billete no es válido — la comprobación está integrada en la puerta, no depende de que alguien se acuerde de mirar.

Los tests solo te protegen si de verdad se ejecutan. Engánchalos a tu pipeline para que se ejecuten automáticamente en cada cambio y bloqueen el merge si fallan — el CI gate del curso de deploy. Esto es lo que convierte los tests de algo que quizá ejecutes en una garantía de que nada roto se fusiona. «Tests o no se publica» es el estándar que vale la pena mantener: un cambio sin probar yendo a producción es una apuesta que no tenías por qué hacer.

Pusha changeCI pipelineruns every testGateall green?Merge ✓into mainBlocked ✗fix firsttests or it didn't ship
Todo cambio pasa por CI: todos los tests en verde — merge; aunque sea uno en rojo — el merge se bloquea.
PracticeCI Gate· comprobación de merge
Problema

Los tests existen, pero la gente se olvida de ejecutarlos — y el código roto aterriza en main.

Idea

CI ejecuta todos los tests en cada cambio y rechaza el merge hasta que estén en verde.

Ejemplo

Un pull request: CI ejecuta los tests, uno falla — el botón de merge queda desactivado.

Coste
Esperar a CI; los flaky tests duelen más aquí.
Cuándo usarlo
En cualquier proyecto con más de un cambio a la semana.
Relaciones

Ejecuta toda la Test Pyramid; no puede convivir con los Flaky Tests; caza Regressions antes que los usuarios.

Cómo encaja todo

Una orquesta: cada instrumento importa, pero la música solo sucede juntos.

Los conceptos de este curso funcionan como un solo sistema. La pirámide fija la forma del conjunto de tests, el TDD lo escribe, los test doubles dan aislamiento a los unit tests, y el CI gate lo ejecuta todo en cada cambio — y eso entrega el premio principal: cambiar el código sin miedo. En contra trabajan los flaky tests y el over-mocking, que minan la confianza en el verde. Y el coverage solo insinúa dónde están los huecos.

Test pyramidshapes the suiteTDDwrites tests firstTest doublesisolate unitsTest suiteunit · integration · e2eOver-mockingfalse greenChange without fearregressions caughtFlaky testsdestroy trustCoveragehints at gapsCI gateruns on every change
La pirámide, el TDD y los test doubles construyen el conjunto; el CI gate lo ejecuta y te deja cambiar sin miedo. Los flaky tests y los mocks de más golpean la confianza; el coverage solo insinúa.
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?
  • ¿Los tests comprueban el comportamiento, no la implementación interna que se rompe en un refactor?
  • ¿Se usan mocks para dependencias externas reales, no para falsear lo que se está probando?
  • ¿Es rápido y determinista — sin flaky tests que erosionen la confianza?
  • ¿Se ejecuta en CI en cada cambio, bloqueando los merges rotos?
Señales de que testeas bien
  • Cambias el código con confianza, fiándote de que el conjunto cazará lo que rompiste.
  • Tus tests forman una pirámide — rápidos en la base, unos pocos realistas arriba.
  • Los tests comprueban el comportamiento y sobreviven a los refactors en lugar de romperse con cada limpieza.
  • El conjunto es rápido y determinista, 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.

Chuleta: los 19 conceptos

Por qué testeamos

Automated Testtest

Código que comprueba código: entrada → llamada → assertion → pasa o falla.

Regressionregression bug

Un cambio rompe lo que antes funcionaba, a menudo lejos de la edición.

Unit tests

Unit Testtest de módulo

Una función en aislamiento, en milisegundos. Tienes miles.

Arrange–Act–AssertAAA

Prepara, haz una cosa, comprueba el resultado.

Integration, e2e y la pirámide

Integration Testcomprobación de integración

Varias partes reales juntas — comprobando las costuras.

End-to-End TestE2E

Todo el sistema como lo ve un usuario. Realista, pero lento y frágil.

Test Pyramidpirámide de testing

Muchos unit, menos integration, unos pocos e2e. No el cono de helado.

Falsos

Test Doublefalso

Un sustituto de una dependencia lenta, cara o aleatoria.

Stubrespuesta enlatada

Devuelve una respuesta preparada — alimenta a tu código con datos.

Mockregistrador de llamadas

Registra llamadas — verifica que ocurrió un efecto secundario.

Over-mockingdemasiados mocks

Todo con mock — el test está en verde mientras el sistema real falla.

TDD y buenos tests

TDDred-green-refactor

Primero un test que falla, luego el mínimo código, luego una limpieza.

Test Behaviour, Not Implementationblack-box testing

Comprueba el resultado por la interfaz pública — el test sobrevive al refactor.

Living Documentationespecificación ejecutable

Los tests son promesas en código que nunca se quedan obsoletas.

Confiar en el conjunto

Code Coveragecobertura de tests

Cuánto código se ejecutó en los tests. Una linterna para los huecos, no una medida de calidad.

Goodhart's Lawmedida vs objetivo

Una medida que se vuelve objetivo deja de medir. 100% de coverage → tests huecos.

Flaky Testtest inestable

Falla al azar. El remedio: hacerlo determinista, o borrarlo.

Usarlo bien

Risk-based Testingtesting por riesgo

Más tests donde el fallo es costoso o probable.

CI Gatecomprobación de merge

Tests en cada cambio; rojo — el merge se bloquea.

Testear bien es criterio: prueba las partes de riesgo, mantén la pirámide, comprueba el comportamiento y no la implementación, y ejecútalo todo automáticamente — para que un conjunto en verde signifique de verdad que es seguro cambiar.

Fin del curso exprés · 7 capítulos · 19 conceptos

Lo que sigue es práctica: toma una función importante y escribe unos cuantos unit tests para ella — arrange, act, assert — luego rompe la función a propósito y mira 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 quédate con una idea por encima de las demás: los tests no sirven para 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 anquilosarse poco a poco.