Nivel inicial19 min de lectura · Revisado el

Tipos de pruebas de software y la pirámide de tests

Tipos de pruebas de software: la pirámide de tests, con muchos unitarios abajo, menos de integración y pocos end to end arriba. La pirámide de testsrubentejera.com end to endla cenaintegraciónel platounitariosla salsa
Como en una cocina: se prueba mucho la salsa (tests unitarios), menos el plato montado (integración) y pocas veces la cena completa servida al cliente (end to end).

Unitario, de integración, end to end, funcional, de aceptación, de regresión, de humo… Cuando empiezas a escribir tests, cada artículo y cada equipo usa nombres distintos, y a veces el mismo nombre para cosas diferentes. Es fácil acabar sin saber qué tipo de test tienes delante ni cuántos hacen falta de cada uno.

Aquí tienes una clasificación clara, apoyada en el temario oficial de ISTQB, la pirámide de tests de Mike Cohn y un ejemplo ejecutado con tres niveles habituales al programar.

Los tipos de pruebas, de un vistazo

Antes de los tipos, el símil que usaremos en todo el artículo: una cocina. Antes de servir una cena, el cocinero prueba la salsa con una cuchara, prueba el plato ya montado y, alguna vez, sigue la cena entera desde que el cliente pide hasta que se le sirve. Son tres pruebas distintas y ninguna sustituye a las otras.

Con los tests pasa lo mismo, y además se pueden mirar desde varios ángulos. Esta tabla es una explicación nuestra a partir del temario de ISTQB:

Se clasifican por… Tipos La pregunta que responde
Nivel (qué tamaño de pieza se prueba) Unitarias, de integración, de sistema (o end to end), de aceptación ¿La salsa, el plato o la cena entera?
Lo que comprueban Funcionales y no funcionales ¿Hace lo que debe o lo hace bien (rápido, seguro, fácil de usar)?
Cómo se diseñan De caja negra y de caja blanca ¿Se piensa desde fuera o mirando el código?
Cuándo se repiten De confirmación y de regresión ¿Se ha arreglado el fallo? ¿Se ha roto algo más?

ISTQB lo dice expresamente: los tipos de prueba (funcional, no funcional, caja negra, caja blanca) se pueden aplicar en todos los niveles. Por eso un test no tiene un único nombre: un test unitario puede ser funcional y de caja blanca a la vez. Si un test no encaja en ninguna casilla, probablemente no está claro qué comprueba.

Por nivel: de la pieza al sistema completo

El temario de ISTQB describe cinco niveles. Al programar, lo habitual es hablar de cuatro, porque las dos integraciones se suelen juntar.

Pruebas unitarias

También llamadas de componente o de unidad. Comprueban una pieza pequeña (una función, una clase) por separado, sin base de datos ni red. Es probar la salsa: si falla, sabes exactamente qué ingrediente es. Las escribe quien programa, en su propio entorno, y se ejecutan en milisegundos.

Qué es una prueba de unidad y cómo se escribe una buena, con las reglas FIRST, está explicado en los tests limpios y las reglas FIRST.

Pruebas de integración

Comprueban que varias piezas funcionan juntas: tu código con la base de datos, con el sistema de ficheros o con otro servicio. Es el plato montado: la salsa estaba bien y la carne también, pero ¿casan? ¿Llega caliente?

ISTQB distingue dos: la integración de componentes (piezas de tu propio programa entre sí) y la integración de sistemas (tu programa con sistemas y servicios externos). Ham Vocke, en «The Practical Test Pyramid», prefiere mantenerlas estrechas: probar un punto de contacto con el exterior cada vez, como la base de datos, y sustituir el resto de servicios por dobles de prueba, en lugar de arrancar el sistema entero.

Son más lentas y más delicadas que las unitarias: necesitan preparar el entorno y, al terminar, dejarlo como estaba.

Pruebas de sistema y end to end

Comprueban el sistema completo, de un extremo al otro (end to end), como lo usaría alguien de verdad: desde la interfaz o la API hasta la base de datos. Es seguir la cena entera, desde que el cliente pide hasta que tiene el plato delante.

Dan la mayor confianza, pero son las más lentas y frágiles. Vocke las describe como notoriamente inestables (flaky): pueden fallar por un tiempo de espera o por un cambio de diseño sin que haya un error real. Para las de navegador se usan herramientas como Selenium, Playwright o Cypress; para una API basta con hacerle peticiones HTTP, como en el ejemplo de abajo.

Pruebas de aceptación

Comprueban que el software cumple lo que necesita el negocio, con ejemplos que se acuerdan con quien lo pide. Lo que las define no es el tamaño de la pieza, sino la pregunta: ¿esto es lo que el cliente quería? Vocke lo dice así: la idea de los tests de aceptación es independiente de la pirámide, y pueden escribirse en distintos niveles.

Cómo se acuerdan antes de programar y cómo se automatizan está en ATDD: desarrollo dirigido por tests de aceptación.

Pruebas funcionales y no funcionales

Es la otra gran división, y no depende del nivel.

  • Pruebas funcionales: comprueban qué hace el sistema. ¿Se calcula bien el envío? ¿Se guarda el pedido? ¿Se rechaza un email mal escrito? En la cocina: ¿el plato es el que se pidió?
  • Pruebas no funcionales: comprueban cómo de bien lo hace. ¿Responde en menos de un segundo? ¿Aguanta mil usuarios a la vez? ¿Se puede usar con un lector de pantalla? En la cocina: ¿sale a tiempo un sábado con el comedor lleno?

Para las no funcionales, ISTQB remite a la norma ISO/IEC 25010, que agrupa así las características de calidad (traducción nuestra):

  • Eficiencia de desempeño: tiempos de respuesta, consumo de recursos, carga que aguanta (las pruebas de rendimiento, de carga y de estrés).
  • Compatibilidad: convive y se entiende con otros sistemas.
  • Usabilidad (también llamada capacidad de interacción): se entiende y se usa con facilidad, también con accesibilidad.
  • Fiabilidad: funciona sin fallos y se recupera cuando algo va mal.
  • Seguridad: protege los datos frente a accesos indebidos.
  • Mantenibilidad: se puede cambiar sin miedo.
  • Portabilidad (o flexibilidad): funciona en otros entornos.
  • Protección frente a daños (safety): no pone en peligro a personas ni bienes.

ISTQB añade dos ideas útiles. Muchas pruebas no funcionales salen de las funcionales: el mismo test que comprueba que se calcula el envío puede comprobar también que tarda menos de un tiempo dado. Y descubrir tarde un problema no funcional puede poner en riesgo el proyecto, así que a veces conviene empezar pronto, incluso en las revisiones o en las pruebas unitarias.

Otros tipos: caja negra, caja blanca, regresión y humo

Cinco nombres más que aparecen a menudo:

  • Caja negra: el test se diseña desde fuera, a partir de lo que el sistema debe hacer, sin mirar el código. Como el cliente que solo prueba el plato.
  • Caja blanca: el test se diseña mirando por dentro, para que recorra el código, la arquitectura o los flujos. Como el jefe de cocina que revisa cada paso de la receta.
  • Confirmación: tras arreglar un fallo, se comprueba que de verdad está arreglado.
  • Regresión: tras cualquier cambio, se comprueba que no se ha roto otra cosa. Para eso sirve tener los tests automatizados: se vuelven a lanzar todos en segundos.
  • Humo (smoke testing): una comprobación rápida de que lo básico funciona, para saber si merece la pena seguir probando. El glosario de ISTQB la define como el tipo de prueba que da confianza suficiente en que algo está listo para las pruebas previstas. En la cocina: encender los fogones antes de abrir.

Qué hace el perfil que suele encargarse de todo esto en un equipo, y en qué se diferencia probar de asegurar la calidad, está en qué es un QA tester.

La pirámide de tests

La pirámide responde a la pregunta práctica: ¿cuántos tests escribo de cada nivel? La propuso Mike Cohn en su libro Succeeding with Agile (2009) y en el artículo «The Forgotten Layer of the Test Automation Pyramid». Tiene tres capas:

  1. Abajo, tests unitarios: la base, la más ancha.
  2. En medio, tests de servicio: prueban la aplicación por debajo de su interfaz de usuario.
  3. Arriba, tests de interfaz de usuario: la punta, la más estrecha.

Cohn quería la menor cantidad posible de tests de interfaz, porque son frágiles, caros y lentos, y muchos repiten caminos que se pueden probar más abajo.

Hoy se suele dibujar con otros nombres (unitarios, integración y end to end), como reconoce el propio temario de ISTQB. Lo que no cambia es la forma. Según ISTQB, cuanto más alta es la capa, más grande es la pieza que se prueba, menos aislado está el test y más tarda en ejecutarse. Abajo hacen falta muchos tests pequeños; arriba bastan unos pocos.

Pirámide de tests: muchos tests unitarios en la base, menos de integración en medio y pocos end to end en la punta, con un eje que indica que hacia arriba los tests son más lentos, más caros y menos aislados, y hacia abajo, más rápidos, más baratos y más aislados.
Fíjate en el eje de la derecha: cuanto más sube un test en la pirámide, más tarda, más cuesta y menos aislado está; por eso arriba bastan unos pocos.

Vocke lo reduce a dos reglas que conviene recordar más que los nombres de las capas:

  1. Escribe tests de distinta granularidad, es decir, de distintos tamaños.
  2. Cuanto más alto el nivel, menos tests.

En la cocina: pruebas la salsa muchas veces mientras la haces, el plato montado alguna vez y la cena completa solo cuando hace falta. Si solo probaras cenas completas, tardarías muchísimo y, cuando algo supiera mal, no sabrías qué ingrediente fue.

El símil se queda corto en algo: en la cocina las catas se repiten a mano. En el software, los tests automatizados se repiten solos con cada cambio, y por eso compensa tener muchos.

Los límites de la pirámide

La pirámide no es la única forma que se propone. Hay quien defiende un panal (honeycomb) o un trofeo, con pocos tests unitarios y la mayoría de integración. Martin Fowler repasa ese debate en «On the Diverse And Fantastical Shapes of Testing» (2021) y señala que parte de la discusión viene de que cada uno llama «unitario» y «de integración» a cosas distintas: lo que unos llaman test de integración, otros lo consideran un test unitario que usa sus colaboradores reales. Y, de la mano de Justin Searls, añade algo más importante que el porcentaje de cada tipo: que los tests sean expresivos, rápidos, fiables y que solo fallen por motivos útiles.

Vocke reconoce lo mismo en su artículo: la comunidad del desarrollo de software «no ha conseguido ponerse de acuerdo en unos términos bien definidos» sobre testing (traducción nuestra). Por eso, cuando alguien te hable de un tipo de test, pregúntale qué quiere decir exactamente.

Qué probar en cada nivel, con un ejemplo

Una tienda online cobra 4,95 € de envío, salvo en los pedidos de 50 € o más, que van gratis. Tiene tres piezas: una función que calcula el total, un almacén que guarda los pedidos en ficheros JSON y un pequeño servidor HTTP que recibe los pedidos. Vamos a probar cada nivel con node:test, el ejecutor de tests que trae Node.js, sin instalar nada.

Unitario: la regla del envío

Se prueba la función sola, con los casos que importan, incluido el límite justo de 50 €. Los importes van en céntimos para evitar errores de redondeo.

// envio.js
export const ENVIO = 495;
export const GRATIS_DESDE = 5000;

export function totalConEnvio(importe) {
  return importe >= GRATIS_DESDE ? importe : importe + ENVIO;
}
// envio.test.js — test unitario: una función, sin disco ni red.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { totalConEnvio } from './envio.js';

test('por debajo de 50 € se suman 4,95 € de envío', () => {
  assert.equal(totalConEnvio(3000), 3495);
});

test('desde 50 € justos, el envío es gratis', () => {
  assert.equal(totalConEnvio(5000), 5000);
});

Qué probar aquí: las reglas y sus límites. Es el sitio más barato para probar muchos casos.

Integración: guardar y leer del disco

Ahora el almacén, con el sistema de ficheros de verdad. El test crea una carpeta temporal y la borra al terminar, para dejar el disco como estaba.

// pedidos.js
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

export function crearAlmacen(carpeta) {
  return {
    async guardar(id, pedido) {
      await writeFile(join(carpeta, `${id}.json`), JSON.stringify(pedido));
    },
    async leer(id) {
      return JSON.parse(await readFile(join(carpeta, `${id}.json`), 'utf8'));
    },
  };
}
// pedidos.test.js — test de integración: nuestro código y el disco de verdad.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { crearAlmacen } from './pedidos.js';

test('un pedido guardado se puede volver a leer', async (t) => {
  const carpeta = await mkdtemp(join(tmpdir(), 'pedidos-'));
  t.after(() => rm(carpeta, { recursive: true }));
  const almacen = crearAlmacen(carpeta);

  await almacen.guardar('p1', { importe: 3000, total: 3495 });

  assert.deepEqual(await almacen.leer('p1'), { importe: 3000, total: 3495 });
});

Qué probar aquí: el punto de contacto con el exterior, y solo ese. No vuelvas a probar la regla del envío: ya está cubierta abajo.

End to end: un pedido de principio a fin

Por último, se arranca el servidor y se le habla por HTTP, como lo haría una aplicación cliente. Vocke lo presenta como un test end to end de API, sin navegador, a medio camino del end to end completo (también lo llama subcutaneous, «subcutáneo»: justo por debajo de la interfaz gráfica). Fíjate en que solo usa HTTP: crea el pedido y lo consulta por la API, sin mirar por dentro del almacén.

// servidor.js
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { totalConEnvio } from './envio.js';

// POST /pedidos con {"importe": 3000} → 201 con el pedido y su total.
// GET /pedidos/<id> → 200 con el pedido guardado.
export function crearServidor(almacen) {
  return createServer(async (peticion, respuesta) => {
    if (peticion.method === 'POST' && peticion.url === '/pedidos') {
      let cuerpo = '';
      for await (const trozo of peticion) cuerpo += trozo;
      const { importe } = JSON.parse(cuerpo);
      const pedido = { id: randomUUID(), importe, total: totalConEnvio(importe) };
      await almacen.guardar(pedido.id, pedido);
      respuesta.writeHead(201, { 'content-type': 'application/json' }).end(JSON.stringify(pedido));
    } else if (peticion.method === 'GET' && peticion.url.startsWith('/pedidos/')) {
      const pedido = await almacen.leer(peticion.url.slice('/pedidos/'.length));
      respuesta.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(pedido));
    } else {
      respuesta.writeHead(404).end();
    }
  });
}
// servidor.test.js — test end to end: el servidor arrancado, solo por HTTP.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { crearAlmacen } from './pedidos.js';
import { crearServidor } from './servidor.js';

test('un pedido de 30 € se crea con su envío y se puede consultar después', async (t) => {
  const carpeta = await mkdtemp(join(tmpdir(), 'pedidos-'));
  const servidor = crearServidor(crearAlmacen(carpeta));
  await new Promise((listo) => servidor.listen(0, listo)); // 0: cualquier puerto libre
  t.after(() => { servidor.close(); return rm(carpeta, { recursive: true }); });
  const url = `http://localhost:${servidor.address().port}/pedidos`;

  const creado = await fetch(url, { method: 'POST', body: JSON.stringify({ importe: 3000 }) });
  const pedido = await creado.json();
  const consultado = await fetch(`${url}/${pedido.id}`);

  assert.equal(creado.status, 201);
  assert.equal(pedido.total, 3495); // la regla ya se prueba abajo: aquí se comprueba que está conectada
  assert.deepEqual(await consultado.json(), pedido);
});

Qué probar aquí: un camino importante de principio a fin, para saber que las piezas están bien conectadas. Uno, no todos los casos. La comprobación del total (3.495 céntimos) no vuelve a probar la regla del envío, que ya está cubierta abajo: comprueba que el servidor usa de verdad esa función.

Lo que enseña la ejecución

Con los cuatro tests en la carpeta (y un package.json con { "type": "module" }):

node --test
✔ por debajo de 50 € se suman 4,95 € de envío (0.707223ms)
✔ desde 50 € justos, el envío es gratis (0.157407ms)
✔ un pedido guardado se puede volver a leer (6.683983ms)
✔ un pedido de 30 € se crea con su envío y se puede consultar después (40.01347ms)
ℹ tests 4
ℹ pass 4
ℹ fail 0

Fíjate en los tiempos de esta ejecución: menos de un milisegundo el unitario, unos 7 el de integración y unos 40 el end to end. En un ejemplo tan pequeño todo es rápido. Los tiempos apuntan en la dirección que explica la pirámide; con un solo test de cada nivel no es una medida. La pirámide habla, sobre todo, de cuántos tests escribir de cada nivel.

Ahora rompemos el código a propósito: cambiamos >= por > en totalConEnvio, de modo que un pedido de 50 € justos pagaría envío.

✔ por debajo de 50 € se suman 4,95 € de envío
✖ desde 50 € justos, el envío es gratis
✔ un pedido guardado se puede volver a leer
✔ un pedido de 30 € se crea con su envío y se puede consultar después
ℹ pass 3
ℹ fail 1

Solo lo detecta el test unitario, y señala la pieza exacta. El end to end sigue en verde porque prueba un pedido de 30 €. Para pillar ese fallo desde arriba harían falta más tests end to end, más lentos, y cuando fallaran no dirían qué pieza tiene la culpa. Es la regla de Vocke para no duplicar tests: lleva cada test tan abajo en la pirámide como puedas, y si uno de arriba encuentra un fallo que ninguno de abajo detecta, escribe el de abajo.

Una última regla de Vocke para decidir qué no probar: no pruebes código trivial, como un getter o un setter que solo devuelve un valor. Prueba el comportamiento que se ve desde fuera, a través de la parte pública del código.

Errores frecuentes

  • La pirámide al revés. Muchos tests end to end y pocos unitarios: la batería tarda, falla por motivos ajenos al código y nadie se fía de ella.
  • Repetir el mismo caso en todos los niveles. Si la regla del envío está probada abajo, arriba basta con comprobar que todo está conectado.
  • Tests de integración que no limpian lo que dejan. Si un test deja datos en la base de datos o ficheros en el disco, el siguiente puede fallar sin motivo.
  • Pelearse por el nombre. Lo importante no es si un test es «unitario» o «de integración» según una definición concreta, sino saber qué comprueba y por qué está en ese nivel. Vocke dedica una sección entera a esta confusión de nombres, y Fowler recuerda que cada autor los usa a su manera.

Cuando los tests los escribe un agente de IA

Esta sección es interpretación nuestra, desde el enfoque IA Limpia.

Si le pides a un agente de código «añade tests», no le has dicho de qué nivel. Puede escribir un test end to end para cada caso o tests unitarios que no tocan nunca la base de datos, y en los dos casos pasarán. La pirámide te da las preguntas para pedirlo y para revisarlo:

  1. Di el nivel. Un ejemplo de petición: «Tests unitarios de totalConEnvio, incluido el límite de 50 €; un solo test end to end del pedido.»
  2. Mira dónde ha puesto cada caso. Las reglas y sus límites, abajo. Arriba, pocos caminos.
  3. Rompe el código a propósito, como en el ejemplo. Si nada falla, falta un test en el nivel donde vive esa regla. Las herramientas de mutation testing, como Stryker, automatizan esa comprobación.

Para decidir en qué orden pedirle los tests unitarios, uno a uno, sirve la técnica ZOMBIES.

Tu siguiente paso

  1. Abre un proyecto tuyo y cuenta cuántos tests tienes de cada nivel. ¿Tiene forma de pirámide?
  2. Busca una regla de negocio que solo esté probada en un test end to end y escríbele su test unitario.
  3. Si quieres que los tests guíen el diseño desde el principio, empieza por qué es TDD.

Fuentes

  • Temario ISTQB Certified Tester Foundation Level v4.0.1 (2024), secciones 2.2.1 (Test Levels), 2.2.2 (Test Types), 2.2.3 (Confirmation Testing and Regression Testing) y 5.1.6 (Test Pyramid). Leído el 9 de noviembre de 2026. La clasificación en tabla y las traducciones son nuestras.
  • Ham Vocke, «The Practical Test Pyramid», martinfowler.com, 26 de febrero de 2018. Leído el 9 de noviembre de 2026: las dos reglas de la pirámide, tests de integración estrechos, tests end to end de API, no probar código trivial y cómo evitar la duplicación. También la frase sobre la terminología (traducción nuestra). Las demás explicaciones son un resumen nuestro.
  • Martin Fowler, «On the Diverse And Fantastical Shapes of Testing», martinfowler.com, 2 de junio de 2021. Leído el 9 de noviembre de 2026: el debate entre pirámide, panal y trofeo, y la falta de acuerdo sobre qué es un test unitario o de integración. Resumen nuestro.
  • Glosario de ISTQB, versión 4.8.1, término smoke testing. Consultado el 9 de noviembre de 2026; la explicación es nuestra.
  • Mike Cohn, «The Forgotten Layer of the Test Automation Pyramid», Mountain Goat Software (última actualización, 7 de febrero de 2023). Leído el 9 de noviembre de 2026. El libro Succeeding with Agile no se ha consultado; la fecha de 2009 sale del temario de ISTQB; Vocke atribuye el concepto al libro.
  • Documentación de node:test para Node 24.
  • Código: todo el código se ha ejecutado con Node.js 24.21.0 (funciona igual en Node 26.13.0) y node:test el 9 de noviembre de 2026, también la versión rota a propósito. Las salidas son las reales, recortadas a las líneas que importan; los tiempos cambian en cada ejecución y de una máquina a otra.