learning-front

Nivel 6 · Introducción al testing

Proyecto: la suite de tests del motor

Mega-tarea de cierre: coge el motor del Team Builder ya tipado y dótalo de una suite completa —unitarios, integración, casos límite y parametrizados— que corra en verde y con cobertura razonable. No hay concepto nuevo: es juntar todo lo del nivel sobre código que ya conoces.

Llegaste al final del nivel. Toca el encargo que lo junta todo: dotar al motor del Team Builder —el que tipaste en el Nivel 5— de una suite de tests completa.

No hay nada nuevo que aprender aquí. Es síntesis: coger lo de cada capítulo y aplicarlo, de una vez, sobre código que ya conoces.

  • Unitarios de cada función, con su caso típico y sus límites (vacío, un elemento, fronteras como winrate 0 y 100, filtros sin coincidencias).
  • Los fallos silenciosos: que las funciones no muten su entrada, que validarHeroes descarte cada tipo de dato corrupto.
  • Parametrizados con it.each para las tablas, aislados con beforeEach, y un builder para no repetir datos.
  • El matcher correcto en cada sitio: toEqual para objetos y arrays, toBeCloseTo para decimales, toThrow para los errores, toHaveLength para los tamaños.
  • La integración con procesar: el pipeline entero, de datos crudos a ranking.
  • Y una mirada a la cobertura (pnpm coverage) para encontrar agujeros —sin perseguir el 100% a ciegas—.

Recuerda que este proyecto se construye sobre el anterior: el motor tipado del Nivel 5 sigue intacto, y tú le añades la red de tests encima. Acumulas una capa más —HTML, JS, tipos, y ahora tests— sin descartar nada. Es exactamente como crece un proyecto de verdad.

Cuando tu suite corra en verde y cubra el motor de punta a punta, habrás cerrado el Nivel 6: sabes escribir tests que protegen sobre lógica pura. Lo siguiente es React (Nivel 7); y en el Nivel 8, el testing vuelve para acompañarlo en el DOM, lo asíncrono y la red.

Comprueba lo que sabes#

Pregunta 1 de 4

Una suite completa del motor, ¿qué debería cubrir de cada función?

Tu turno#

Este es un proyecto en local: el testing vive en tu terminal. Clona la carpeta, escribe la suite completa y déjala en verde con pnpm test. Mira la cobertura con pnpm coverage (la primera vez te pedirá instalar el proveedor @vitest/coverage-v8; acepta y listo). Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en el salto de un nivel al siguiente: de los unitarios del caso feliz a una suite que cubre el motor entero, integración incluida.

Ejercicio · hazlo en local

La suite de tests completa del motor

Coge el motor del Team Builder ya tipado (validarHeroes con Zod, enriquecer, filtrarPorRol, rankearPorWinrate, agruparPorRol y procesar) y dótalo de una suite de tests completa que corra en verde. Cubre cada función por su caso típico, sus límites y su fallo silencioso, prueba la integración del pipeline, y mira la cobertura. No hay concepto nuevo: junta todo lo del nivel.

Paso 1: Unitarios de las funciones núcleo

  • enriquecer, filtrarPorRol y rankearPorWinrate tienen tests del caso típico que pasan en verde.
  • Comparas los resultados con el matcher adecuado (toEqual, toHaveLength), no a ojo.
  • Los nombres de los tests describen el comportamiento, no la implementación.

Cómo hacerlo en local

Clona el repositorio del curso, entra en la carpeta del ejercicio y abre el index.html en tu navegador. Toda tu solución va en solucion.js.

git clone <repo>
cd exercises/nivel-6/proyecto-suite-del-motor
# abre index.html en el navegador y edita solucion.js
Ver soluciones
// motor.test.ts — tier OK: tests unitarios de las funciones núcleo, caso feliz.
import {
  enriquecer,
  filtrarPorRol,
  rankearPorWinrate,
  type Heroe,
} from "./motor";
import { describe, it, expect } from "vitest";

describe("enriquecer", () => {
  it("calcula el winrate como victorias entre partidas", () => {
    const heroes: Heroe[] = [
      { nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6 },
    ];
    expect(enriquecer(heroes)[0].winrate).toBe(60);
  });
});

describe("filtrarPorRol", () => {
  it("devuelve solo los héroes del rol pedido", () => {
    const heroes = enriquecer([
      { nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6 },
      { nombre: "Reinhardt", rol: "Tanque", partidas: 8, victorias: 5 },
    ]);
    expect(filtrarPorRol(heroes, "Tanque").map((h) => h.nombre)).toEqual([
      "Reinhardt",
    ]);
  });

  it("con 'todos' no descarta a nadie", () => {
    const heroes = enriquecer([
      { nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6 },
      { nombre: "Reinhardt", rol: "Tanque", partidas: 8, victorias: 5 },
    ]);
    expect(filtrarPorRol(heroes, "todos")).toHaveLength(2);
  });
});

describe("rankearPorWinrate", () => {
  it("ordena de mayor a menor winrate", () => {
    const heroes = enriquecer([
      { nombre: "Reinhardt", rol: "Tanque", partidas: 8, victorias: 2 },
      { nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6 },
    ]);
    expect(rankearPorWinrate(heroes).map((h) => h.nombre)).toEqual([
      "Genji",
      "Reinhardt",
    ]);
  });
});

Por qué este nivel

  • Tests unitarios del caso feliz de las tres funciones núcleo, con matchers y nombres correctos. Es una base sólida: si alguien rompe el cálculo del winrate o el orden del ranking, salta.
  • Su límite: ni límites ni integración ni validación. El caso típico está cubierto; los bordes —donde viven los bugs— todavía no.