learning-front

Nivel 6 · Introducción al testing

Anatomía de un test: describe, it y expect

El esqueleto de todo test: agrupar con describe, declarar casos con it, afirmar con expect, y el patrón Arrange-Act-Assert. Nombrar los tests por comportamiento, no por implementación, para que el output se lea como documentación.

En el capítulo anterior copiaste un esqueleto describe / it / expect sin entrar en qué era cada pieza. Vamos a diseccionarlo. Y a partir de aquí cambia una cosa: las demos corren dentro de esta página, con los matchers reales de Vitest, para que no tengas que saltar a la terminal en cada ejemplo. Lo que ves en verde o rojo aquí es lo mismo que verías con pnpm test.

Las tres piezas: describe, it, expect#

Todo test se construye con tres funciones, cada una con un trabajo distinto:

  • describe(título, fn)agrupa tests relacionados bajo un título. No comprueba nada por sí misma: organiza. En el informe, su título encabeza sus casos.
  • it(nombre, fn) — declara un caso de test: un nombre en lenguaje llano y una función con el cuerpo de la prueba. (test es su sinónimo exacto; usa el que lea mejor.)
  • expect(valor) — afirma el resultado. Encadenado con un matcher (toBe, y muchos más que verás en el próximo capítulo), declara qué debería cumplirse. Si no se cumple, lanza un error y el test se pone en rojo.

Aquí están las tres en acción. Pulsa «Correr tests» (o edita y vuelve a correr) y mira el panel de resultados: verde es que pasa.

Cada expect(...).toBe(...) es una aserción: una comprobación concreta. Un test puede tener una o varias; si cualquiera falla, el test entero falla.

El patrón Arrange-Act-Assert#

¿Te has fijado en los comentarios Arrange, Act, Assert del ejemplo? No son decoración: son el patrón con el que se estructura cualquier test, en tres pasos siempre en el mismo orden:

  1. Arrange (preparar) — montas los datos de entrada: el héroe, el array, lo que la función necesita.
  2. Act (actuar) — ejecutas la función bajo prueba, una sola vez, y guardas el resultado.
  3. Assert (afirmar) — compruebas ese resultado con expect.
typescript
it("calcula el winrate de un héroe", () => {
  // Arrange: el escenario.
  const heroes: Heroe[] = [{ nombre: "Genji", partidas: 10, victorias: 6 }];
  // Act: la acción que se prueba.
  const resultado = enriquecer(heroes);
  // Assert: la comprobación.
  expect(resultado[0].winrate).toBe(60);
});

Seguir siempre este orden hace que cualquier test —tuyo o de un compañero— se lea igual: primero el escenario, luego la acción, luego la afirmación. Cuando la preparación es trivial, Act y Assert pueden ir en la misma línea (expect(winrate(6, 10)).toBe(60)), pero el orden mental es el mismo.

El nombre del test es documentación#

El nombre que le pones a un it no es un detalle: es una frase que aparece en el informe y que cuenta qué debe hacer el código. La regla de oro: describe el comportamiento, no la implementación.

typescript
// MAL: no dice nada, o describe el CÓMO interno.
it("test1", ...);
it("funciona", ...);
it("llama a map y a Math.round", ...);

// BIEN: describe el QUÉ, el comportamiento observable.
it("calcula el winrate como victorias entre partidas", ...);

¿Y qué tiene de malo “llama a map y a Math.round”? Que si mañana reescribes enriquecer con un for en vez de un map —mismo resultado, otra implementación—, ese nombre miente: describe un interior que ya no existe. El nombre que habla de comportamiento sigue siendo cierto. Por eso una suite bien nombrada se lee como una especificación: pasas la vista por los nombres y entiendes qué hace el motor sin leer una línea de su código.

Cuando un test falla#

Un test en verde tranquiliza, pero el que enseña es el que se pone en rojo. Mira qué pasa cuando una aserción no se cumple: este test afirma a propósito un valor equivocado.

Verás algo como ✗ winrate › convierte 6 de 10 en 60% y, debajo, expected 60 to be 70. Eso es lo que hace útil un fallo: te dice qué caso se rompió y qué esperaba frente a qué recibió. No es un misterio, es una pista. Cambia el 70 por 60 en el editor, vuelve a correr y míralo pasar a verde.

Acostúmbrate a ver tus tests fallar al menos una vez. Una suite que solo has visto en verde no demuestra que proteja nada: hasta que no la ves saltar cuando rompes algo, no sabes si de verdad vigila.

Comprueba lo que sabes#

Pregunta 1 de 5

¿Cuál es el papel de `describe`?

Tu turno#

Tienes motor.ts con enriquecer y filtrarPorRol, y un motor.test.ts con un test de ejemplo y dos TODO. Completa la suite siguiendo el esqueleto y el patrón AAA. Recuerda: de momento solo conoces toBe; para tamaños, expect(algo.length).toBe(n). Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en el salto de un nivel al siguiente.

Ejercicio · en esta página

Escribe la anatomía de los tests del motor

Tienes motor.ts (enriquecer y filtrarPorRol, ya escritas) y un motor.test.ts con un test de ejemplo. Completa la suite: añade casos siguiendo el esqueleto describe → it → expect y el patrón Arrange-Act-Assert. Solo conoces el matcher toBe; para tamaños usa expect(x.length).toBe(n).

Paso 1: Más casos, en verde

  • Añades al menos un segundo it dentro del describe de enriquecer (otro héroe, otro winrate).
  • Cada test sigue el esqueleto describe → it → expect y pasa (todo en verde).
  • Los nombres de los it describen el caso, no son "test1" ni "funciona".
Ver soluciones
// motor.test.ts — tier OK: dos casos de enriquecer, esqueleto correcto.
import { describe, it, expect } from "vitest";
import { enriquecer, type Heroe } from "./motor";

describe("enriquecer", () => {
  it("calcula el winrate de un héroe", () => {
    // Arrange: 6 victorias de 10 partidas.
    const heroes: Heroe[] = [
      { nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6 },
    ];
    // Act
    const resultado = enriquecer(heroes);
    // Assert: 6/10 = 60%.
    expect(resultado[0].winrate).toBe(60);
  });

  it("calcula el winrate de otro héroe", () => {
    // Arrange: 2 victorias de 8 partidas.
    const heroes: Heroe[] = [
      { nombre: "Reinhardt", rol: "Tanque", partidas: 8, victorias: 2 },
    ];
    // Act
    const resultado = enriquecer(heroes);
    // Assert: 2/8 = 25%.
    expect(resultado[0].winrate).toBe(25);
  });
});

Por qué este nivel

  • Dos casos de enriquecer con nombres que describen el caso: cumple el esqueleto y pasa. Suficiente para "correcto".
  • Su límite: solo prueba una función, y los dos casos son casi iguales (mismo camino, distinto número). No toca filtrarPorRol ni el patrón AAA de forma explícita.