Saltar al contenido

Principios Fundamentales: F.I.R.S.T.

Fast, Independent, Repeatable, Self-Validating, Timely — la checklist mnemotécnica para unit tests, y por qué Fast y Repeatable son los que más problemas causan en la práctica.

3 min. de lectura

Una regla mnemotécnica muy usada para pensar unit tests es F.I.R.S.T., atribuida a Tim Ottinger y Jeff Langr, y popularizada por Clean Code de Robert C. Martin. No es un estándar formal ni una especificación: funciona como checklist de buenas prácticas.

PrincipioQué significaRiesgo si se ignora
FastCorre en milisegundos, sin arrancar servicios realesFricción para correr la suite seguido → se corre menos
IndependentNo depende de otros tests ni de shared stateFailures en cascada sin relación con el bug real
RepeatableMismo resultado siempre, bajo las mismas condicionesFlaky tests que erosionan la confianza en la suite
Self-ValidatingDetermina por sí mismo si pasó o falló, sin revisión manualNo se puede correr en CI/CD sin intervención humana
TimelySe escribe cerca del momento en que se implementa el comportamientoEl test documenta código viejo en vez de guiar el nuevo

Vale la pena detenerse en dos de estos principios, porque son los que más problemas causan en la práctica.

Fast

No tiene sentido que un test de una business rule dependa de levantar la aplicación, la base de datos y un request HTTP cuando el comportamiento se puede verificar sin ninguna de esas dependencias. La velocidad no es un objetivo aislado: habilita un ciclo de feedback corto, y permite asociar un failure con los cambios más recientes.

Repeatable

Un test que falla de forma intermitente —un flaky test— es especialmente costoso, porque hace difícil distinguir un defecto real de un problema de timing o de infraestructura. Las causas más comunes son la hora actual, randomness, shared state, race conditions y dependencias de red.

Por ejemplo, si calculateCancellationFee tomara la hora de la reserva y calculara internamente cuántas horas faltan con new Date(), el resultado del test dependería del momento exacto en que se ejecuta. La solución habitual es inyectar una abstracción controlable:

function calculateCancellationFee(
  deposit: Money,
  reservationTime: Date,
  clock: Clock = systemClock
): Money {
  const hoursBeforeReservation = clock.hoursUntil(reservationTime);
  if (hoursBeforeReservation >= 24) return Money.zero();
  return deposit.multiply(0.5);
}

Así el test puede definir explícitamente el instante utilizado, en lugar de depender de cuándo se corre la suite. El mismo principio se aplica a la generación de IDs, los valores random, las variables de entorno y cualquier otro input no determinista.

Fijate que esta versión resuelve dos cosas: calcula las horas restantes y decide la penalidad. La alternativa —dejar calculateCancellationFee(deposit, hoursBeforeReservation) como una función pura y calcular las horas afuera— es determinista sin necesidad de inyectar nada. La forma más barata de tener un test repetible suele ser no dejar entrar el no-determinismo en la unidad, antes que controlarlo con una abstracción. Los artículos que siguen usan esa versión pura.

Con la unidad bajo test aislada y determinista, el siguiente problema es cómo estructurar el test en sí — de eso trata el patrón AAA.

Te sirvió, compartilo

// ¿te sirvió?
// compartilo