Nivel avanzado10 min de lectura · Publicado el

Inside-out TDD y outside-in TDD: arquitectura y diseño

Inside-out y outside-in TDD como un puzle: se monta desde los grupos de piezas del centro o desde el borde, con un mock en el hueco. TDD: ¿desde dentro o fuera?rubentejera.com inside-outdel centro afueraoutside-inmockdel borde adentro
Inside-out junta primero grupos de piezas del centro (objetos reales) y luego los une. Outside-in empieza por el borde, lo que pide el usuario, y pone en cada hueco un mock que define la forma de la pieza que falta.

Sabes hacer el ciclo de TDD (test en rojo, código mínimo, refactorizar), pero ante una funcionalidad nueva surge la duda: ¿por qué test empiezo? ¿Por la clase más pequeña o por el botón que pulsa el usuario? Esa decisión marca el diseño que saldrá, y por eso hablamos de arquitectura.

Piensa en un puzle. Puedes empezar por el centro: juntas grupos de piezas que encajan entre sí y, cuando los tienes, los unes (inside-out). O puedes empezar por el borde, lo que se ve desde fuera: sabes qué forma tiene el conjunto y vas rellenando hacia dentro, aunque todavía no sepas qué pieza irá en cada hueco (outside-in). El símil tiene un límite: en un puzle no puedes tapar un hueco con una pieza de mentira; en un test, sí, y eso son los mocks.

Las dos escuelas de un vistazo

Inside-out (clásica)

  • Empieza por el dominio y sube hacia la interfaz
  • Usa objetos reales siempre que puede
  • Comprueba el estado final
  • El diseño aparece al refactorizar

Outside-in (Londres)

  • Empieza por el caso de uso y baja hacia el dominio
  • Usa mocks para lo que aún no existe
  • Comprueba los mensajes entre objetos
  • El diseño aparece al escribir el test
Las dos escuelas de TDD sobre tres capas: interfaz, caso de uso y dominio. Inside-out, la clásica, empieza por el dominio y sube hacia la interfaz, con objetos reales, y comprueba el estado final. Outside-in, la de Londres, empieza por el caso de uso y baja hacia el dominio, con mocks para lo que aún no existe, y comprueba los mensajes.
Cada flecha sale del punto por el que empieza su escuela: inside-out, del dominio hacia arriba; outside-in, del caso de uso hacia abajo.

Martin Fowler lo explica en «Mocks Aren’t Stubs». En el estilo clásico se usan objetos reales si es posible y un doble de prueba solo cuando el real es incómodo; en el mockista se usa un mock para cualquier objeto con un comportamiento interesante. Fowler añade que a veces se llama «Detroit» al estilo clásico y «Londres» al mockista. El nombre «Chicago» también es habitual para la escuela clásica, aunque Fowler no lo usa.

Si los términos mock, stub o spy te suenan pero no los distingues, empieza por la kata de stubs y spies.

Inside-out: de dentro hacia fuera

Empiezas por la regla de negocio más pequeña que puedes probar sola. Cuando funciona, la usas desde la capa de arriba, y así hasta llegar a la interfaz. Fowler describe una variante clásica que llama middle-out: decides qué objetos del dominio necesita la funcionalidad y añades la interfaz al final, a menudo sin ningún doble de prueba.

Ejemplo: registrar un usuario y dejarle un email de bienvenida. Primero, la pieza interna:

// usuario.mjs
export function crearUsuario(email) {
  if (!email.includes('@')) throw new Error('Email no válido');
  return { email: email.trim().toLowerCase() };
}
// usuario.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { crearUsuario } from './usuario.mjs';

// Paso 1: empezamos por la pieza más interna, el dominio.
test('normaliza el email', () => {
  assert.equal(crearUsuario('  [email protected] ').email, '[email protected]');
});

test('rechaza un email sin arroba', () => {
  assert.throws(() => crearUsuario('ana'), /no válido/);
});

Después subes una capa y usas esa pieza real. El almacén y el buzón son simples listas en memoria, y el test mira cómo quedan al final:

// registro.mjs
import { crearUsuario } from './usuario.mjs';

export function registrarUsuario(email, { usuarios, buzon }) {
  const usuario = crearUsuario(email);
  usuarios.push(usuario);
  buzon.push({ para: usuario.email, asunto: 'Bienvenida' });
  return usuario;
}
// registro.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { registrarUsuario } from './registro.mjs';

// Paso 2: subimos una capa con piezas reales y comprobamos el estado final.
test('guarda al usuario y le deja un email de bienvenida', () => {
  const usuarios = [];
  const buzon = [];

  registrarUsuario('[email protected]', { usuarios, buzon });

  assert.deepEqual(usuarios, [{ email: '[email protected]' }]);
  assert.deepEqual(buzon, [{ para: '[email protected]', asunto: 'Bienvenida' }]);
});

Ventajas: los tests no dependen de cómo se llaman los objetos entre sí, así que puedes refactorizar por dentro sin romperlos. Riesgo: construir piezas que luego nadie usa, o descubrir tarde que el caso de uso necesitaba otra cosa.

Outside-in: de fuera hacia dentro

Empiezas por lo que pide el usuario. Steve Freeman y Nat Pryce lo desarrollan en «Growing Object-Oriented Software, Guided by Tests» (GOOS, 2009), el libro de referencia de este estilo según Fowler. Se trabaja en dos bucles:

  1. Escribes un test de aceptación de toda la funcionalidad. Fallará un buen rato.
  2. Bajas una capa y escribes un test unitario del objeto que atiende ese caso de uso.
  3. Sus colaboradores todavía no existen: los sustituyes por mocks y decides qué mensajes deben recibir.
  4. Implementas cada colaborador con el mismo ciclo, capa a capa, hasta que el test de aceptación pasa.

El mismo registro, empezando por fuera:

// registro.test.mjs
import { test, mock } from 'node:test';
import assert from 'node:assert/strict';
import { registrarUsuario } from './registro.mjs';

// El repositorio y el notificador aún no existen: los sustituimos
// por mocks y el test decide qué mensajes deben recibir.
test('guarda al usuario y pide que le den la bienvenida', () => {
  const repositorio = { guardar: mock.fn() };
  const notificador = { darBienvenida: mock.fn() };

  registrarUsuario('[email protected]', { repositorio, notificador });

  const usuario = { email: '[email protected]' };
  assert.deepEqual(repositorio.guardar.mock.calls[0].arguments, [usuario]);
  assert.deepEqual(notificador.darBienvenida.mock.calls[0].arguments, [usuario]);
});
// registro.mjs
export function registrarUsuario(email, { repositorio, notificador }) {
  const usuario = { email };
  repositorio.guardar(usuario);
  notificador.darBienvenida(usuario);
  return usuario;
}

Fíjate en lo que ha pasado: antes de escribir una línea del repositorio o del notificador, el test ya ha decidido que existen y qué métodos tienen. Fowler cuenta que los mockistas defienden un estilo llamado need-driven development: cada capa descubre lo que necesita de la siguiente.

Ventajas: solo construyes lo que el caso de uso pide y las interfaces nacen de su uso real. Riesgo: los tests saben cómo hablan los objetos entre sí. Si renombras guardar, el test se rompe aunque el comportamiento sea el mismo. Y un test lleno de mocks puede pasar aunque las piezas reales no encajen; para eso está el test de aceptación del bucle exterior.

Inside-out pregunta qué sé hacer. Outside-in pregunta qué me piden.

Qué tiene que ver con la arquitectura

Mucho, porque cada escuela empuja hacia una forma de diseño:

  • Outside-in encaja con la arquitectura hexagonal (puertos y adaptadores). Los mocks del ejemplo, repositorio y notificador, son justo los puertos: contratos que el caso de uso define y que luego implementan una base de datos o un servicio de correo. Esta relación es una interpretación de este artículo, no de los autores: GOOS habla de objetos que se envían mensajes y de escuchar a los tests, no de hexágonos.
  • Inside-out favorece un dominio rico y probado sin dobles, que es el corazón de cualquier arquitectura limpia. Su riesgo es que la frontera con el exterior se diseñe tarde y a toda prisa.

No son bandos. El propio Kent Beck escribió en el prólogo de GOOS: «The style of test-driven development presented here is different from what I practice» («el estilo de TDD que se presenta aquí es distinto del que practico»), y aun así recomienda el libro. Una forma habitual de combinarlos: outside-in para las fronteras y la orquestación del caso de uso, inside-out para la lógica de negocio pura.

Si… Prueba con
La lógica de negocio es compleja (cálculos, reglas) Inside-out
La funcionalidad coordina varios servicios o capas Outside-in
Aún no sabes qué necesita el usuario Outside-in, con un test de aceptación primero
Quieres tests que aguanten refactorizaciones grandes Inside-out, o mocks solo en las fronteras

El test de aceptación del bucle exterior es la misma idea que el ATDD: lo tienes explicado en desarrollo dirigido por tests de aceptación.

En una aplicación con el patrón modelo vista controlador, outside-in suele empezar por el controlador, que es la puerta de entrada de cada petición, e inside-out por el modelo.

Y cuando programas con IA

Como criterio del enfoque IA Limpia, las dos escuelas sirven para poner límites a un agente:

  • Outside-in: escribe tú el test de aceptación antes de pedir nada. Es el contrato que el agente no puede tocar y la prueba de que el resultado hace lo que pediste.
  • Inside-out: pide piezas pequeñas, cada una con su test, y revísalas antes de subir de capa. Es más fácil revisar una función de diez líneas que un caso de uso entero.
  • Vigila los mocks: un agente puede generar tests que solo comprueban que el código se llama a sí mismo como ya lo hace. Si un test no fallaría ante un error real, no protege nada.

El vídeo original

Este artículo nació para acompañar el vídeo «Introducción a outside-in TDD con JavaScript», del canal de YouTube «Carlitos y sus cosas» (@carlosble), de Carlos Blé.

Qué es La grabación de una clase con la Generación K, del 15 de junio de 2018
De qué trata Según su descripción: inside-out TDD, outside-in TDD, arquitectura y diseño de software
Duración 1 h 19 min, sin capítulos
Idioma Español
Precio Gratis en YouTube

Es una clase grabada, no un vídeo editado: conviene verla con tiempo y, si puedes, con el editor abierto para repetir los ejemplos en JavaScript. Tiene unos años, así que la sintaxis o las herramientas que aparezcan pueden haber cambiado; las ideas de las dos escuelas siguen igual. Los ejemplos de este artículo, con node:test de Node 24, sirven como versión actualizada para practicar.

Siguiente paso

Elige una funcionalidad pequeña y hazla dos veces: una empezando por el dominio y otra empezando por el caso de uso. Después compara qué tests se rompen al cambiar un nombre de método. Esa diferencia es la que te ayudará a elegir en tu próximo proyecto.

Fuentes

  • Martin Fowler, «Mocks Aren’t Stubs» (revisado el 2 de enero de 2007): definición de los estilos clásico y mockista, los nombres «Detroit» y «Londres», need-driven development, outside-in y middle-out, y la recomendación de Freeman y Pryce. Consultado en noviembre de 2026.
  • Steve Freeman y Nat Pryce, Growing Object-Oriented Software, Guided by Tests, Addison-Wesley, primera impresión de octubre de 2009. La cita de Kent Beck procede del prólogo, leído en la muestra oficial de Pearson.
  • El nombre «escuela de Chicago» es de uso común en la comunidad, pero no se ha localizado en una fuente primaria; por eso no se atribuye a nadie.
  • Documentación de node:test (Node.js 24.x): mock.fn() y mock.calls[i].arguments.
  • Datos del vídeo (título, canal, fecha, duración y descripción) comprobados en su página de YouTube y en su oEmbed el 7 de noviembre de 2026. El contenido del vídeo no se ha transcrito: este artículo no atribuye a Carlos Blé nada de lo que se explica arriba.
  • Los dos ejemplos se han ejecutado con node --test en Node.js 24.21.0: los tres tests de inside-out y el de outside-in pasan.