Saltar al contenido principal

El Trofeo del Testing 🏆

Qué es y de dónde viene

El Testing Trophy es un modelo de distribución de esfuerzo en testing propuesto por Kent C. Dodds en 2018, como evolución del Testing Pyramid clásico de Martin Fowler (2012). Fue inspirado por el tweet de Guillermo Rauch (creador de Socket.io, fundador de Vercel):

"Write tests. Not too many. Mostly integration."

La idea central: el retorno sobre la inversión (ROI) de los tests no es uniforme. Escribir más tests no da necesariamente más confianza — importa qué tipo de tests escribes y en qué proporción.


El Trofeo vs. la Pirámide

AspectoPirámide (Fowler, 2012)Trofeo (Dodds, 2018)
Capa más grandeUnit testsIntegration tests
Premisa principalLos E2E son lentos y caros — evítalosLas herramientas mejoraron; el ROI cambia
Incluye análisis estáticoNoSí (base de la pirámide)
FocoVelocidad de ejecuciónConfianza real por inversión de tiempo
Contexto originalBackend / lenguajes compiladosFrontend / JavaScript / TypeScript

La Pirámide asume que los E2E son prohibitivamente lentos y costosos. Esa premisa era cierta en 2012 con Selenium. Con Playwright y Vitest Browser Mode en 2025, ya no lo es.

El principio guía del Trofeo:

"The more your tests resemble the way your software is used, the more confidence they can give you." — Kent C. Dodds


Estructura del Trofeo


/E\ ← pocos, lentos, máxima confianza
/2E \
/─────\
/ \
/ Integr. \ ← LA MAYOR INVERSIÓN
/ tests \
/─────────────\
/ Unit tests \ ← rápidos, bajo costo, confianza acotada
/─────────────────\
/ Static analysis \ ← TypeScript, ESLint — sin costo de ejecución
────────────────────

Nivel 0 — Análisis estático (base, costo cero en runtime)

TypeScript y ESLint detectan errores antes de ejecutar cualquier código. No son "tests" en sentido estricto pero proveen la primera capa de confianza sin costo de ejecución. En Wiedii son obligatorios en todos los proyectos JS/TS.

Qué captura: errores de tipos, variables no usadas, imports mal formados, violaciones de convención. Qué NO captura: lógica de negocio incorrecta, interacciones entre módulos.

Nivel 1 — Unit tests (base del trofeo)

Prueban una única función, clase u objeto en aislamiento total — sus dependencias están mockeadas o ausentes.

Cuándo son valiosos:

  • Lógica pura (funciones matemáticas, parsers, transformadores de datos)
  • Algoritmos con muchos casos borde (binary search, validaciones complejas)
  • Utilidades independientes reutilizadas en múltiples lugares

Cuándo NO son la respuesta:

  • Cuando la función A llama a la función B — un unit test de A con B mockeada no prueba que A+B trabajen juntas
  • Cuando el comportamiento que importa es la interacción entre capas

Regla práctica: antes de escribir un unit test, pregunta: ¿un integration test que ya existe cubre este path? Si sí, el unit test es redundante.

Nivel 2 — Integration tests (el corazón del trofeo — LA MAYOR INVERSIÓN)

Prueban múltiples unidades interactuando entre sí — con tan poco mocking como sea posible.

Son el núcleo del Trofeo porque ofrecen el mejor balance entre:

  • Velocidad de escritura y ejecución (mucho más rápidos que E2E)
  • Confianza real (prueban comportamiento observable, no implementación)
  • Resistencia al refactor (si cambias cómo está implementado algo pero el comportamiento es el mismo, el test no debe romperse)

Qué mockear en integration tests:

  • Servicios externos reales (APIs de terceros, pasarelas de pago) — usar MSW u otro interceptor HTTP
  • Efectos secundarios costosos (emails, notificaciones push)

Qué NO mockear:

  • Tu propia lógica de negocio
  • Tu propia base de datos (usar una instancia de prueba real o en memoria)
  • Tus propios módulos internos

Señal de que tienes demasiados mocks: si tu integration test mockeó 5 cosas para probar una función, probablemente es un unit test disfrazado.

Nivel 3 — Property-based tests (capa transversal — adición de Wiedii)

Los tests de propiedad prueban invariantes que deben sostenerse para cualquier input dentro de un dominio, no para inputs concretos hardcodeados.

Wiedii usa fast-check integrado con bun:test. Son complementarios — no reemplazan unit ni integration tests, sino que los refuerzan cuando existe lógica con muchos valores de entrada posibles.

import * as fc from 'fast-check';
import { describe, test, expect } from 'bun:test';

describe('serialize / deserialize — invariante de ida y vuelta', () => {
test('siempre recupera el valor original', () => {
fc.assert(
fc.property(fc.record({ id: fc.uuid(), name: fc.string() }), (obj) => {
expect(deserialize(serialize(obj))).toEqual(obj);
}),
);
});
});

Cuándo añadir property tests:

  • Funciones de serialización / parseo — propiedad: deserialize(serialize(x)) === x
  • Operaciones conmutativas o asociativas
  • Funciones de ordenamiento — propiedad: el resultado siempre está ordenado
  • Validadores — propiedad: inputs válidos nunca se rechazan, inputs inválidos nunca se aceptan
  • Cualquier función donde "para todos los X válidos, Y debe cumplirse"

Cuándo NO añadir property tests:

  • Comportamientos con semántica específica por caso (reglas de negocio que cambian por país, por tipo de usuario, etc.)
  • Flujos de UI — ahí van integration o E2E tests

Nivel 4 — End-to-End tests (cima del trofeo)

Prueban el sistema completo sin mocks — exactamente como lo usaría un usuario real. Desde la UI hasta la base de datos, pasando por toda la pila.

Son los tests que dan más confianza por definición, pero históricamente eran los más costosos. En 2025 esto cambió: con Playwright, los E2E son significativamente más rápidos y confiables que con Selenium. Kent C. Dodds ha planteado públicamente que en algunos contextos modernos (SSR, full-stack frameworks), el E2E podría convertirse en la capa más grande del trofeo.

Qué cubrir con E2E:

  • Los flujos críticos de negocio — login, checkout, onboarding, operaciones irreversibles
  • Happy paths de las features más importantes
  • Flujos que cruzan muchos servicios y donde una integration test no puede cubrir la integración real

Qué NO cubrir con E2E:

  • Casos borde — son lentos y costosos para eso; los cubren los integration tests
  • Cada posible estado de UI — usa integration tests con estados simulados

Estado actual en la industria (2025-2026)

El Trofeo es la referencia más citada en el ecosistema JavaScript/TypeScript. El panorama actual:

ArquitecturaModelo más adoptadoProporción
MonolitosTesting Pyramid~61%
MicroserviciosTesting Honeycomb (Spotify)~48%
Serverless / FaaSTesting Trophy~42%
Full-stack JS/TSTesting Trophymayoría

Ningún modelo es universalmente correcto. El consenso actual en la industria:

  • Para lógica de dominio compleja (reglas de negocio, algoritmos, cálculos) → más unit tests, la Pirámide funciona bien
  • Para aplicaciones orientadas a API, SSR o flujos de usuario → el Trofeo es superior
  • La mayoría de proyectos exitosos usan un híbrido: muchos integration tests como columna vertebral, unit tests donde la lógica es compleja, E2E para journeys críticos

La pregunta que se está debatiendo activamente en 2025-2026 (incluido el propio Kent C. Dodds): con Playwright y el browser mode de Vitest haciendo los E2E tan baratos como los integration tests, ¿deberían los E2E convertirse en la mayor inversión? La respuesta aún no es definitiva, pero la tendencia apunta a que los E2E van a crecer en proporción relativa a medida que las herramientas mejoran.


Cómo aplica en Wiedii

Por tipo de proyecto

Tipo de proyectoModeloInversión principal
Web app / API / SSRTesting TrophyIntegration tests
CLI tool pura (sin DB, sin web)Pyramid adaptadoUnit tests + integration de comandos
Librería/paquete reutilizablePyramidUnit tests exhaustivos
Monorepo con múltiples capasTrophy por capaIntegration entre paquetes

Distribución orientativa del esfuerzo

E2E → ~10-15% (journeys críticos, no casos borde)
Property-based → ~5-10% (invariantes de lógica pura)
Integration → ~60-70% (EL NÚCLEO — aquí va la mayor inversión)
Unit → ~15-20% (lógica compleja aislada)
Static → 100% (TypeScript + ESLint en todo el código)

Estos porcentajes son orientativos — no son reglas fijas. El objetivo es la confianza, no alcanzar un número.

Framework por tipo de proyecto

Tipo de proyectoFramework de testingNotas
CLI tools (sin DB, sin web)bun:testNativo, rápido, suficiente
Web apps / APIs / full-stackVitest (a evaluar)Ver bun-testing para la política completa
Property-based (cualquier tipo)fast-check + el runner del proyectoCompatible con bun:test y Vitest
E2EPlaywrightEstándar de facto en 2025

Qué NO hacer

Demasiados unit tests con mocks excesivos — el error más común al intentar "cubrir" código. Un test que mockea 80% de las dependencias de una función da falsa confianza: el código puede pasar los tests y romperse en producción porque la integración real falla.

E2E para todo — la cima del trofeo es pequeña por una razón. Usar E2E para probar cada campo de formulario o cada mensaje de error hace los tests frágiles, lentos y difíciles de mantener.

100% de cobertura de código como objetivo — la cobertura es un indicador, no el objetivo. El 100% puede lograrse con tests que no detectan bugs reales. El objetivo es la confianza en que el sistema funciona.


El principio que lo resume todo

"Write tests. Not too many. Mostly integration." — Guillermo Rauch / Kent C. Dodds

Tres instrucciones en ocho palabras:

  1. Write tests — sí, los tests salvan tiempo y ahorran llamadas a las 2am
  2. Not too many — los rendimientos decrecen pasado cierto punto; 100% de coverage no es el objetivo
  3. Mostly integration — esta es la inversión con mejor ROI: prueban comportamiento real, resisten refactors, son más rápidos que E2E y más confiables que unit tests aislados

Referencias