Skip to main content

Tipos de prueba según el nivel del sistema

Cuando se habla de pruebas en una aplicación web o en un sistema de juego como Eldoria Chronicles, no basta con distinguir entre caja negra y caja blanca. También es necesario entender qué parte del sistema se está probando y hasta dónde llega el alcance de la prueba. De ahí surge la clasificación por niveles: unitarias, integración, sistema y aceptación.

Cada nivel responde a una pregunta distinta y cumple un papel concreto dentro de la calidad global del software.

Pruebas unitarias: piezas pequeñas bajo control

Las pruebas unitarias validan componentes pequeños y aislados. En Node.js suelen aplicarse a funciones puras, validaciones, cálculos o métodos de negocio que no dependen del servidor HTTP ni de la base de datos. Son rápidas de ejecutar y muy precisas: cuando fallan, el origen del error suele estar claramente localizado.

En Eldoria Chronicles, una función de cálculo de daño es un ejemplo clásico de prueba unitaria. No interesa el combate completo ni el estado del juego, solo comprobar que la función devuelve el valor correcto según sus entradas.

En este nivel se prueban:

– Casos normales (ataque mayor que defensa).

– Casos límite (ataque igual a defensa).

– Casos extremos (ataque menor que defensa, valores cero).

Una prueba unitaria es como comprobar una pieza de un reloj por separado. Antes de ensamblarlo todo, necesitas saber que cada engranaje gira correctamente por sí mismo.

Este tipo de pruebas permite aplicar técnicas como partición de equivalencia y análisis de valores límite sin necesidad de simular escenarios complejos.

Pruebas Unitarias - Eldoria Chronicles

Ejemplo 1: Función de Daño

// CÓDIGO DE LA FUNCIÓN

/**
* Calcula el daño infligido en un combate considerando ataque y defensa
*
* @param {number} ataque - Puntos de ataque del personaje atacante
* @param {number} defensa - Puntos de defensa del personaje defensor
* @returns {number} Daño mínimo 1 (siempre causa algún daño)
*
* Lógica:
* - Daño base = ataque - defensa
* - Si ataque > defensa: daño positivo (ej: 50-30 = 20)
* - Si ataque <= defensa: daño sería 0 o negativo
* - Math.max(1, ...) garantiza un daño mínimo de 1
*/
function calcularDaño(ataque, defensa) {
return Math.max(1, ataque - defensa);
}

// PRUEBAS UNITARIAS

/**
* PRUEBA 1: Caso normal - Ataque mayor que defensa
*
* Escenario: El atacante es más fuerte que el defensor
* Objetivo: Verificar que el cálculo de diferencia funciona correctamente
* Valor esperado: 50 - 30 = 20
*/
test('Ataque mayor que defensa', () => {
expect(calcularDaño(50, 30)).toBe(20);
});

/**
* PRUEBA 2: Caso límite - Ataque igual a defensa
*
* Escenario: Ataque y defensa están equilibrados
* Objetivo: Verificar que se aplica el daño mínimo (regla de daño mínimo 1)
* Valor esperado: 1 (no 0, por la regla de daño mínimo)
*/
test('Ataque igual a defensa', () => {
expect(calcularDaño(50, 50)).toBe(1); // Mínimo 1
});

/**
* PRUEBA 3: Caso extremo - Ataque menor que defensa
*
* Escenario: El defensor es más fuerte que el atacante
* Objetivo: Verificar que el daño mínimo siempre se aplica
* Valor esperado: 1 (aunque 30-50 = -20, el mínimo es 1)
*/
test('Ataque menor que defensa', () => {
expect(calcularDaño(30, 50)).toBe(1); // Mínimo 1
});

// PRUEBAS ADICIONALES RECOMENDADAS

/**
* PRUEBA 4: Valores en cero y negativos (boundary testing)
* Esto verifica el comportamiento con entradas inesperadas
*/
describe('Casos con valores límite y edge cases', () => {

test('Ataque cero', () => {
expect(calcularDaño(0, 10)).toBe(1); // Ataque 0 siempre da 1
});

test('Defensa cero', () => {
expect(calcularDaño(50, 0)).toBe(50); // Defensa 0, daño completo
});

test('Ambos cero', () => {
expect(calcularDaño(0, 0)).toBe(1); // 0-0=0, pero mínimo es 1
});

test('Ataque negativo', () => {
expect(calcularDaño(-10, 5)).toBe(1); // ¿Comportamiento esperado?
});

test('Defensa negativa', () => {
expect(calcularDaño(10, -5)).toBe(15); // Defensa negativa aumenta daño
});
});

/**
* PRUEBA 5: Verificación de tipos de datos
* Para prevenir errores de tipo en tiempo de ejecución
*/
describe('Validación de tipos de datos', () => {

test('Ataque como string numérico', () => {
// Esto podría fallar o comportarse inesperadamente
// Depende de si JavaScript convierte automáticamente
// expect(calcularDaño("50", "30")).toBe(20);
});

test('Valores nulos o undefined', () => {
// Importante para robustez
// expect(calcularDaño(null, 30)).toThrow();
});
});

/**
* ANÁLISIS DE LAS PRUEBAS ACTUALES:
*
* Cubren las 3 clases de equivalencia principales:
* 1. Ataque > Defensa (clase válida con daño positivo)
* 2. Ataque = Defensa (caso límite - valor mínimo)
* 3. Ataque < Defensa (clase válida con daño mínimo)
*
* Técnicas de testing aplicadas:
* - Partición de equivalencia: 3 clases identificadas
* - Análisis de valores límite: casos 50/50 y 30/50
* - Pruebas de reglas de negocio: daño mínimo de 1
*/

Resumen de cobertura de pruebas:

Caso de pruebaValoresResultadoPropósito
Ataque > Defensa50, 3020Caso normal
Ataque = Defensa50, 501Caso límite
Ataque < Defensa30, 501Caso extremo
Recomendado:
Ataque = 00, 101Valor límite inferior
Defensa = 050, 050Valor límite inferior
Valores decimales45.5, 30.215.3Precisión numérica

Las pruebas actuales son sólidas para los casos principales, pero podrían complementarse con pruebas de edge cases y validación de tipos para mayor robustez.

Pruebas unitarias orientadas a objetos

Cuando la lógica está encapsulada en clases, las pruebas unitarias se centran en el comportamiento de cada método. En Eldoria, la clase Personaje ilustra bien este enfoque: recibir daño, morir, curarse o validar parámetros son responsabilidades internas que deben comprobarse de forma aislada.

Aquí no se prueba el sistema de combate completo, sino preguntas concretas:

– ¿Qué ocurre cuando el daño es menor que la vida?

– ¿Qué ocurre cuando el daño es igual o mayor que la vida?

– ¿Cómo se comporta el sistema ante valores inválidos?

El objetivo no es acumular pruebas, sino cubrir todas las decisiones lógicas relevantes del código.

Pruebas de integración: piezas que colaboran

Las pruebas de integración verifican que varias partes del sistema funcionan correctamente juntas. Aquí ya no se prueba una función aislada, sino la interacción entre componentes.

En un backend típico, un ejemplo sería una ruta de Express que valida datos, accede a SQLite y devuelve una respuesta. En Eldoria Chronicles, los ejemplos más claros aparecen cuando se combinan personajes, armas, enemigos e inventario.

Un escenario de integración bien planteado comprueba:

– Que un personaje puede atacar a un enemigo.

– Que el daño se aplica correctamente.

– Que el arma pierde durabilidad.

– Que el estado del enemigo se actualiza.

Las pruebas de integración son como comprobar que un motor funciona una vez ensambladas todas sus piezas. Cada componente puede estar bien por separado, pero el fallo aparece cuando interactúan.

Este nivel es donde suelen aflorar errores de frontera: estados mal sincronizados, efectos secundarios olvidados o dependencias implícitas entre módulos.

Pruebas de Integración - Eldoria Chronicles

// -------------------------------------------------------------------
// PRUEBA 1: Interacción entre Personaje, Arma y Enemigo
// -------------------------------------------------------------------

/**
* PRUEBA: Personaje con espada ataca a goblin
*
* Objetivo: Verificar la interacción completa del sistema de combate
* que involucra tres entidades principales:
* 1. Personaje (con arma equipada)
* 2. Arma (con durabilidad)
* 3. Enemigo (con vida y defensa)
*
* Flujo esperado:
* - Personaje ataca a Enemigo con Arma
* - Arma calcula daño basado en sus estadísticas
* - Enemigo recibe daño (considerando su defensa)
* - Arma sufre desgaste de durabilidad
* - Se registra el resultado del ataque
*/
test('Personaje con espada ataca a goblin', () => {
// ARRANGE: Preparar todos los objetos necesarios para la prueba
const espada = new Arma('Espada de Acero', 15); // Arma con 15 de daño base
const personaje = new Personaje('Aragorn', 100, espada); // Personaje con 100 HP equipado con espada
const goblin = new Enemigo('Goblin', 50, 5); // Enemigo con 50 HP y 5 de defensa

// ACT: Ejecutar la acción principal que queremos probar
const resultado = personaje.atacar(goblin);

// ASSERT: Verificar que todos los efectos secundarios son correctos

// 1. Verificar que el ataque fue exitoso
expect(resultado.exito).toBe(true);

// 2. Verificar que el goblin recibió daño (vida reducida)
expect(goblin.vida).toBeLessThan(50); // La vida inicial era 50, debe ser menor después del ataque

// 3. Verificar que la espada sufrió desgaste
expect(espada.durabilidad).toBeLessThan(100); // Durabilidad inicial probablemente 100, debe reducirse

// ASSERT ADICIONALES RECOMENDADOS:
// expect(goblin.vida).toBe(50 - (15 - 5)); // Si daño = ataque - defensa: 50 - (15-5) = 40
// expect(espada.durabilidad).toBe(99); // Si cada ataque reduce 1 punto de durabilidad
// expect(resultado.danio).toBeGreaterThan(0); // El daño infligido debe ser positivo
});

// -------------------------------------------------------------------
// PRUEBA 2: Sistema de Inventario + Combate
// -------------------------------------------------------------------

/**
* PRUEBA: Usar poción desde inventario durante combate
*
* Objetivo: Verificar la integración entre:
* 1. Sistema de inventario
* 2. Sistema de combate (daño recibido)
* 3. Sistema de items/consumibles
*
* Flujo esperado:
* - Personaje tiene inventario con pociones
* - Personaje recibe daño en combate (vida baja)
* - Personaje usa poción del inventario
* - La poción cura al personaje
* - La poción se consume (elimina del inventario)
*/
test('Usar poción desde inventario durante combate', () => {
// ARRANGE: Configurar escenario completo
const personaje = new Personaje('Mago', 30); // Personaje frágil con 30 HP
const inventario = new Inventario(); // Inventario vacío
const pocion = new Pocion('Curación', 20); // Poción que cura 20 HP

// Configurar inventario del personaje
inventario.agregar(pocion); // Añadir poción al inventario
personaje.asignarInventario(inventario); // Asignar inventario al personaje

// ACT - Parte 1: Personaje recibe daño en combate
personaje.recibirDaño(25); // Recibe 25 de daño
// Vida esperada después del daño: 30 - 25 = 5 HP

// ACT - Parte 2: Personaje usa poción para curarse
const curado = personaje.usarItem('Curación'); // Intentar usar la poción

// ASSERT: Verificar todos los efectos del proceso

// 1. Verificar que el uso de la poción fue exitoso
expect(curado).toBe(true);

// 2. Verificar que la vida del personaje se restauró correctamente
// Vida inicial: 30
// Daño recibido: -25 → Vida: 5
// Curación: +20 → Vida: 25
expect(personaje.vida).toBe(25);

// 3. Verificar que la poción fue consumida (eliminada del inventario)
expect(inventario.contar('Curación')).toBe(0); // Debería tener 0 pociones de ese tipo

// ASSERT ADICIONALES RECOMENDADOS:
// expect(personaje.estaVivo()).toBe(true); // Personaje debería seguir vivo
// expect(inventario.tieneItem('Curación')).toBe(false); // No debería tener la poción
// expect(personaje.vida).toBeLessThanOrEqual(30); // No puede curar más de su vida máxima
});

// -------------------------------------------------------------------
// PRUEBAS ADICIONALES RECOMENDADAS PARA MAYOR COBERTURA
// -------------------------------------------------------------------

describe('Pruebas adicionales para sistema de combate e inventario', () => {

/**
* PRUEBA: Arma se rompe después de múltiples usos
* Objetivo: Verificar sistema de durabilidad de armas
*/
test('Espada se rompe después de durabilidad agotada', () => {
const espadaFragil = new Arma('Espada Frágil', 10, 3); // Durabilidad: 3 usos
const personaje = new Personaje('Guerrero', 100, espadaFragil);
const esqueleto = new Enemigo('Esqueleto', 40, 2);

// Atacar 3 veces (durabilidad se agota)
personaje.atacar(esqueleto);
personaje.atacar(esqueleto);
personaje.atacar(esqueleto);

// Intentar 4to ataque (debería fallar o hacer menos daño)
const resultado = personaje.atacar(esqueleto);

expect(espadaFragil.estaRota()).toBe(true);
expect(resultado.exito).toBe(false); // o daño reducido
});

/**
* PRUEBA: Inventario lleno no puede agregar más items
* Objetivo: Verificar límites de capacidad del inventario
*/
test('No se puede agregar poción a inventario lleno', () => {
const inventario = new Inventario(2); // Capacidad: 2 slots
const pocion1 = new Pocion('Curación', 20);
const pocion2 = new Pocion('Mana', 15);
const pocion3 = new Pocion('Veneno', 10);

inventario.agregar(pocion1); // Slot 1
inventario.agregar(pocion2); // Slot 2

// Intentar agregar tercer item (debería fallar)
const agregado = inventario.agregar(pocion3);

expect(agregado).toBe(false);
expect(inventario.espacioDisponible()).toBe(0);
});

/**
* PRUEBA: Usar item que no existe en inventario
* Objetivo: Verificar manejo de errores/estados inválidos
*/
test('Intentar usar item que no existe en inventario', () => {
const personaje = new Personaje('Ladron', 50);
const inventario = new Inventario();
personaje.asignarInventario(inventario);

// Intentar usar item inexistente
const resultado = personaje.usarItem('Poción Inexistente');

expect(resultado).toBe(false);
expect(personaje.vida).toBe(50); // Vida no debería cambiar
});

/**
* PRUEBA: Combate múltiple - varios personajes vs varios enemigos
* Objetivo: Verificar interacciones complejas del sistema
*/
test('Combate en equipo contra múltiples enemigos', () => {
const guerrero = new Personaje('Guerrero', 100, new Arma('Hacha', 20));
const mago = new Personaje('Mago', 60, new Arma('Báculo', 15));
const goblin1 = new Enemigo('Goblin', 50, 5);
const goblin2 = new Enemigo('Goblin', 50, 5);

// Guerrero ataca al primer goblin
guerrero.atacar(goblin1);

// Mago ataca al segundo goblin
mago.atacar(goblin2);

// Los goblins contraatacan
goblin1.atacar(guerrero);
goblin2.atacar(mago);

// Verificaciones
expect(goblin1.estaVivo() || goblin2.estaVivo()).toBe(true); // Al menos uno vivo
expect(guerrero.vida).toBeLessThan(100);
expect(mago.vida).toBeLessThan(60);
});
});

Resume de cobertura de pruebas:

Componente ProbadoPrueba OriginalPruebas Adicionales
Personaje + Arma + Enemigo✅ Básica✅ Durabilidad
Inventario + Items✅ Básica✅ Capacidad límite
Sistema de Combate✅ Simple✅ Múltiples actores
Manejo de Errores✗ No cubierto✅ Items inexistentes
Estados de Items✅ Pociones✅ Armas rotas
Interacción Compleja✗ No cubierto✅ Combate en equipo

Consideraciones de implementación

¿El inventario es parte del personaje o un componente separado?

  1. ¿Los enemigos también pueden tener inventario/armas?
  2. ¿Qué pasa si el personaje muere durante el combate?
  3. ¿Las armas pueden ser reparadas o son desechables?
  4. ¿Existen efectos de estado (veneno, parálisis)?
  5. ¿Cómo se manejan los ataques críticos/fallos?

Integración con lógica de juego

En sistemas más ricos, como inventarios o comercio, las pruebas de integración validan flujos completos pero acotados. Por ejemplo:

– Recibir daño y usar una poción desde el inventario.

– Comprar un objeto y actualizar oro e inventario.

– Fallar una compra por falta de oro o inventario lleno.

No se busca cubrir todos los casos posibles, sino asegurar que las colaboraciones principales funcionan como un todo coherente.

Pruebas de sistema: el conjunto en funcionamiento

Las pruebas de sistema evalúan la aplicación completa funcionando como una unidad. Se ejecuta el backend, se conecta la base de datos y se validan flujos completos desde el inicio hasta el final.

En Eldoria Chronicles, este nivel aparece cuando se simulan situaciones como:

– Combates con varios personajes y enemigos.

– Sistemas completos de comercio.

– Gestión de clanes con creación, invitaciones y chat interno.

Aquí no interesa tanto cómo se implementa cada clase, sino que el sistema global sea estable, consistente y predecible bajo condiciones reales.

Una prueba de sistema es como conducir el coche por carretera. No solo importa que el motor funcione, sino que frene, gire y responda correctamente en conjunto.

Este tipo de pruebas suele ser más lento y costoso, pero aporta una visión global imprescindible.

Pruebas de aceptación: la perspectiva del usuario

Las pruebas de aceptación validan que el sistema aporta el valor esperado por el usuario final. No buscan detalles técnicos ni rutas internas del código. Se centran en criterios claros de negocio.

En Eldoria Chronicles, una misión completa es un ejemplo perfecto:

– El jugador acepta la misión.

– Cumple los objetivos definidos.

– La misión se completa correctamente.

– El jugador recibe recompensas y el estado del juego se actualiza.

Si este flujo funciona, el sistema cumple su propósito desde el punto de vista del jugador, aunque internamente sea complejo.

Las pruebas de aceptación responden a una única pregunta: “¿Esto hace lo que el usuario espera poder hacer, sin errores ni sorpresas?”

Por eso suelen ser el último filtro antes de considerar una aplicación lista para uso real.

Ejemplo : Historia de Usuario Completa

Parte 1: Prueba Principal y Análisis

// -------------------------------------------------------------------
// HISTORIA DE USUARIO / PRUEBA DE ACEPTACIÓN
// -------------------------------------------------------------------

/**
* HISTORIA DE USUARIO:
* "Como jugador, quiero completar una misión de caza de lobos
* para obtener recompensas y progresar en el juego"
*
* CRITERIOS DE ACEPTACIÓN:
* 1. Jugador puede aceptar misión del tablón
* 2. Jugador debe matar exactamente 5 lobos
* 3. Misión se marca como completada tras matar 5 lobos
* 4. Jugador recibe recompensas: oro, items y experiencia
* 5. Estado del juego se actualiza correctamente
*/
describe('Misión: Cazar 5 lobos', () => {

/**
* PRUEBA: Flujo completo de la misión
*
* Objetivo: Validar todo el ciclo de vida de una misión
* 1. Aceptación de misión
* 2. Ejecución de objetivos
* 3. Finalización de misión
* 4. Entrega de recompensas
*
* Nota: Prueba async porque guardar progreso podría ser asíncrono
*/
test('Flujo completo de misión', async () => {
// -------------------------------------------------------------
// PASO 1: Aceptar la misión
// -------------------------------------------------------------

/**
* Jugador interactúa con tablón y acepta misión "Cazar lobos"
* Estados de misión: disponible → en progreso → completada
*/
const mision = tablonMisiones.aceptar('Cazar lobos');

// Verificar misión aceptada correctamente
expect(mision.estado).toBe('en progreso');

// -------------------------------------------------------------
// PASO 2: Cazar los lobos (cumplir objetivo)
// -------------------------------------------------------------

/**
* Jugador mata 5 lobos. Cada lobo:
* - Creado como instancia de Enemigo
* - Atacado por el personaje
* - Debe quedar en estado "muerto"
*/
for (let i = 0; i < 5; i++) {
const lobo = new Enemigo('Lobo', 30); // Nombre: Lobo, Vida: 30
personaje.atacar(lobo);
expect(lobo.estaMuerto()).toBe(true);
}

// -------------------------------------------------------------
// PASO 3: Completar la misión
// -------------------------------------------------------------

/**
* Jugador regresa al tablón para completar misión
* Sistema valida objetivos y prepara recompensas
*/
const completada = tablonMisiones.completar(mision.id);
expect(completada).toBeDefined();

// -------------------------------------------------------------
// PASO 4: Verificar recompensas recibidas
// -------------------------------------------------------------

/**
* Recompensas típicas en misiones de caza:
* - Oro/moneda del juego
* - Items/objetos
* - Experiencia para subir de nivel
*/

// 4.1 Verificar recompensa de oro
expect(completada.recompensa.oro).toBe(100);

// 4.2 Verificar recompensa de items (piel de lobo)
expect(personaje.inventario.tiene('Piel de lobo')).toBe(true);

// 4.3 Verificar recompensa de experiencia
expect(personaje.experiencia).toBeGreaterThan(0);
});

// -------------------------------------------------------------
// PRUEBAS ADICIONALES BÁSICAS
// -------------------------------------------------------------

/**
* PRUEBA: Misión fallida por abandonar
* Escenario: Jugador abandona misión antes de completarla
*/
test('Misión abandonada no da recompensas', () => {
const mision = tablonMisiones.aceptar('Cazar lobos');

// Matar solo 2 lobos y abandonar
personaje.atacar(new Enemigo('Lobo', 30));
personaje.atacar(new Enemigo('Lobo', 30));

tablonMisiones.abandonar(mision.id);

expect(mision.estado).toBe('abandonada');
expect(personaje.inventario.tiene('Piel de lobo')).toBe(false);
});

/**
* PRUEBA: Misión con requisitos previos
* Escenario: Misión requiere nivel mínimo
*/
test('Misión con requisito de nivel mínimo', () => {
personaje.nivel = 2; // Nivel bajo

// Intentar aceptar misión que requiere nivel 5
expect(() => tablonMisiones.aceptar('Cazar lobos alpha')).toThrow();
expect(tablonMisiones.estaDisponible('Cazar lobos alpha')).toBe(false);
});
});

Parte 2: Implementación y Mejoras

// -------------------------------------------------------------------
// ANÁLISIS DE LA PRUEBA DE ACEPTACIÓN
// -------------------------------------------------------------------

/**
* FORTALEZAS DE ESTA PRUEBA:
* 1. Prueba flujo completo de negocio
* 2. Perspectiva real del usuario
* 3. Valida integración entre sistemas
* 4. Claro paso a paso
* 5. Verifica efectos secundarios
*
* MEJORAS SUGERIDAS:
* 1. Usar datos de prueba más robustos
* 2. Probar casos de error (edge cases)
* 3. Verificaciones más específicas
* 4. Mejor aislamiento con mocks
*/

// -------------------------------------------------------------------
// IMPLEMENTACIÓN SUGERIDA DE LAS CLASES
// -------------------------------------------------------------------

/**
* EJEMPLO DE TABLÓN DE MISIONES:
*/
class TablonMisiones {
constructor() {
this.misiones = new Map();
this.misionesDisponibles = this.cargarMisiones();
}

aceptar(nombreMision) {
const mision = this.obtenerMisionDisponible(nombreMision);
if (!mision) throw new Error('Misión no disponible');

mision.estado = 'en progreso';
personaje.agregarMision(mision);
return mision;
}

completar(idMision) {
const mision = this.obtenerMision(idMision);
if (!mision.objetivosCumplidos()) {
throw new Error('Objetivos no cumplidos');
}

mision.estado = 'completada';
const recompensas = mision.entregarRecompensas(personaje);
return { mision, recompensa: recompensas };
}

abandonar(idMision) {
const mision = this.obtenerMision(idMision);
mision.estado = 'abandonada';
personaje.removerMision(mision);
}
}

/**
* EJEMPLO DE MISIÓN DE CAZA:
*/
class MisionCazarLobos {
constructor() {
this.id = generarId();
this.nombre = 'Cazar lobos';
this.estado = 'disponible';
this.objetivo = { tipo: 'matar', enemigo: 'Lobo', cantidad: 5 };
this.progresoActual = 0;
this.recompensa = { oro: 100, items: ['Piel de lobo'], experiencia: 50 };
}

registrarMuerte(enemigo) {
if (enemigo.nombre === 'Lobo' && this.estado === 'en progreso') {
this.progresoActual++;
}
}

objetivosCumplidos() {
return this.progresoActual >= this.objetivo.cantidad;
}

entregarRecompensas(personaje) {
personaje.recibirOro(this.recompensa.oro);
this.recompensa.items.forEach(item => personaje.inventario.agregar(item));
personaje.ganarExperiencia(this.recompensa.experiencia);
return this.recompensa;
}
}

Relación entre niveles de prueba

Cada nivel cubre un tipo de riesgo distinto:

– Las unitarias detectan errores lógicos locales.

– Las de integración descubren fallos en la colaboración entre componentes.

– Las de sistema validan la estabilidad global.

– Las de aceptación confirman el valor real para el usuario.

Ningún nivel sustituye a los demás. Una base sólida de pruebas unitarias no evita problemas de integración, y una prueba de aceptación exitosa no garantiza que el código interno sea mantenible.

Conclusión: profundidad progresiva, no redundancia

La clave no está en probar todo en todos los niveles, sino en elegir el nivel adecuado para cada tipo de problema. Las pruebas bien diseñadas avanzan desde lo simple a lo complejo, desde funciones aisladas hasta flujos completos de usuario.

Este enfoque progresivo es lo que convierte las pruebas en una herramienta educativa, técnica y profesional, en lugar de una acumulación confusa de casos difíciles de mantener.