learning-front

Nivel 6 · Introducción al testing

Unit testing de funciones puras

Probar el motor del Team Builder función a función: el caso feliz, los límites (lista vacía, un elemento, valores frontera) y el caso que falla en silencio. Por qué una función pura —misma entrada, misma salida, sin efectos— es lo más fácil de testear, y por qué eso es una razón para escribirlas así.

Ya tienes las herramientas: describe, it, expect y los matchers. Toca usarlas en serio sobre el motor del Team Builder —el que tipaste en el Nivel 5—. Y empezamos por entender por qué ese motor es tan fácil de probar: porque sus funciones son puras.

Por qué una función pura es fácil de testear#

Una función pura cumple dos cosas: con la misma entrada da siempre la misma salida, y no produce efectos secundarios (no muta sus argumentos, no toca variables de fuera, ni el DOM, ni la red). enriquecer, filtrarPorRol y rankearPorWinrate son así: entra un array, sale un array nuevo, y nada más cambia en el mundo.

Eso las vuelve triviales de testear: las llamas y compruebas lo que devuelven. No hay que montar un navegador, ni un servidor, ni un estado global, ni limpiar nada entre tests.

typescript
// Probar una función pura: una línea de preparación, una de acción, una de afirmación.
const ranking = rankearPorWinrate(heroes);
expect(ranking[0].nombre).toBe("Genji");

Compáralo con una función con efecto secundario —digamos, una que empuja el resultado a un array global en vez de devolverlo—: para testearla tendrías que preparar ese array, llamar a la función, y luego inspeccionar el array de fuera para ver qué pasó, y vaciarlo antes del siguiente test. Más montaje, más frágil. Por eso el motor se escribió puro: la testabilidad no es casualidad, es una decisión de diseño. Cuando puedas elegir, escribe funciones puras.

Tres cosas que probar de cada función#

Para cada función, no te quedes en “¿funciona con datos normales?”. Prueba tres cosas:

  1. El caso feliz — la entrada típica produce la salida esperada.
  2. Los límites — las entradas extremas: una lista vacía, un solo elemento, los valores frontera (un winrate de 0, de 100). Es donde se esconden los bugs.
  3. El caso que falla en silencio — un resultado que parece bien pero está mal.

Aquí está rankearPorWinrate probada así: el caso feliz, el límite de la lista vacía y la garantía de que no muta la entrada.

El fallo que no se ve#

El caso más peligroso es el que no lanza ningún error. Mira esta versión de rankearPorWinrate con un bug sutil: ordena de menor a mayor por una resta invertida.

No revienta, no avisa: devuelve un array perfectamente válido, solo que en el orden equivocado. Mirando dos héroes podrías no darte cuenta; en producción, el ranking del Team Builder saldría del revés y los peores héroes aparecerían arriba, sin un solo error en la consola. El test afirma el comportamiento esperado —“el de mayor winrate va primero”— y caza justo eso. Ese es el «¿y qué?»: un bug silencioso no se ve a ojo, pero un test lo convierte en rojo.

Comprueba lo que sabes#

Pregunta 1 de 5

¿Por qué una función pura es la más fácil de testear?

Tu turno#

Completa la suite del motor: para cada función, el caso feliz, los límites y el caso que falla en silencio. Empieza por los TODO de rankearPorWinrate. 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

Prueba el motor función a función

Tienes el motor (enriquecer, filtrarPorRol, rankearPorWinrate, las tres puras) y un test con el caso feliz de rankearPorWinrate. Completa la suite cubriendo, para cada función, el caso feliz, los límites y el caso que falla en silencio.

Paso 1: Caso feliz bien comprobado

  • rankearPorWinrate ordena de mayor a menor: lo compruebas con varios héroes, no solo dos.
  • Comparas el resultado con toEqual (la lista de nombres o el array), no a ojo.
Ver soluciones
// motor.test.ts — tier OK: el caso feliz de rankearPorWinrate, bien comprobado.
import { describe, it, expect } from "vitest";
import { rankearPorWinrate, type HeroeEnriquecido } from "./motor";

describe("rankearPorWinrate", () => {
  it("ordena de mayor a menor winrate", () => {
    const heroes: HeroeEnriquecido[] = [
      { nombre: "Reinhardt", rol: "Tanque", partidas: 8, victorias: 2, winrate: 25 },
      { nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6, winrate: 60 },
      { nombre: "Mercy", rol: "Apoyo", partidas: 20, victorias: 15, winrate: 75 },
    ];
    // Comparamos la lista de nombres: refleja el orden por winrate (75, 60, 25).
    const nombres = rankearPorWinrate(heroes).map((h) => h.nombre);
    expect(nombres).toEqual(["Mercy", "Genji", "Reinhardt"]);
  });
});

Por qué este nivel

  • Comprueba el caso feliz con TRES héroes y compara la lista de nombres con toEqual: si el orden se rompe, el test lo dice. Suficiente para "correcto".
  • Su límite: solo prueba el caso típico de una función. Con una lista vacía o un solo héroe no sabemos qué pasa; ahí es donde se esconden los bugs.