Apuntes DAM
Tema 3 de 7ED · 1º DAM/DAW

Entornos de Desarrollo

Diseño y realización de pruebas

Técnicas de diseño de pruebas: caja blanca, caja negra, casos de prueba y tipos de pruebas del software.

30 min lecturaIntermedioRevisado el

Introducción al diseño y realización de pruebas

Cuando un equipo de desarrollo acepta el encargo de crear una aplicación informática, desea que el resultado de su trabajo sea de calidad. Esto significa que la aplicación debe responder a las necesidades del cliente y contener el menor número posible de errores.

El objetivo de la etapa de pruebas, posterior a la programación, es descubrir defectos antes de que el software sea entregado al cliente.

En esta unidad se explica cómo abordar esta importante tarea y las diferentes técnicas de diseño de casos de prueba. También se verá de qué manera un entorno de desarrollo puede asistir a los programadores en las tareas de depuración o corrección de errores encontrados durante esta fase.

Para finalizar, se describe el empleo de una herramienta de pruebas unitarias que permite realizar pruebas de manera automatizada.

Objetivo de la unidad

Aprenderás a diseñar y ejecutar pruebas de software de forma sistemática, utilizando técnicas y herramientas que mejoran la calidad y fiabilidad de las aplicaciones.


Filosofía de las pruebas de software

Las pruebas constituyen una de las tareas del ciclo de vida del software y tienen como objetivo fundamental la detección de defectos cometidos inadvertidamente durante la construcción del software.

Objetivo de las pruebas

El objetivo principal no es demostrar que el software no tiene errores, sino detectar fallos de manera eficiente, equilibrando el esfuerzo requerido y la probabilidad de encontrar defectos.

Detectar defectos no significa que los desarrolladores sean negligentes. Todos somos humanos, y los errores son inevitables. Por ello, las pruebas buscan localizar fallos antes de que los descubra el cliente.

Otro factor a considerar es la percepción psicológica: mientras que análisis, diseño y programación se consideran tareas constructivas, las pruebas pueden percibirse como destructivas. Esto puede llevar a que los desarrolladores diseñen pruebas para demostrar que el software funciona en lugar de descubrir errores. Es fundamental superar esta mentalidad.

Actitud frente a las pruebas

Según Piattini et al. (2007): «Un buen caso de prueba tiene alta probabilidad de encontrar un defecto no descubierto; el éxito consiste en detectar defectos no encontrados previamente». Esto implica cambiar la mentalidad tradicional de pruebas superficiales y trabajar de manera sistemática.

En proyectos grandes, se suele involucrar un equipo de pruebas independiente que colabora con los desarrolladores para garantizar pruebas exhaustivas.

Objetivos de la prueba

  1. Comprobar si se está construyendo el producto correcto (validación).
  2. Verificar que el producto funciona correctamente (verificación).
Es decir, se debe comprobar tanto si el producto cumple los deseos del cliente como si está correctamente construido.

Además, hay dos aspectos estratégicos que considerar:

  1. Estrategia de aplicación: decidir qué partes del software se someterán a prueba a medida que se desarrolla.
  2. Técnicas de diseño de casos de prueba: una vez seleccionada la parte a probar, decidir cómo realizar la prueba usando la técnica apropiada.

Definición de caso de prueba según IEEE

«Un conjunto de entradas, condiciones de ejecución y resultados esperados desarrollados para un objetivo particular, como ejercitar un camino concreto de un programa o verificar el cumplimiento de un requisito».

En otras palabras, un caso de prueba indica:

  • Qué datos de entrada se deben suministrar al programa.
  • Qué resultados se esperan obtener.
  • Cuál es el objetivo de la prueba.

Tras ejecutar la prueba, se comprueba si la salida coincide con la esperada. Si coincide, no se detectó defecto; si no coincide, se realiza la depuración, que consiste en localizar y corregir el error. Luego, se pueden realizar nuevos casos de prueba.

Diseño de pruebas

Pruebas en el desarrollo de Software



Estrategias de pruebas del software

Las pruebas siempre comienzan por los componentes más pequeños, es decir, se empieza probando pequeñas porciones de software y se va incrementando de manera progresiva el alcance de la prueba. Se suelen llevar a cabo cuatro tipos de pruebas secuenciales que se exponen en los siguientes subapartados.

PRUEBA DE UNIDAD

Se comienza probando o bien módulos individuales de software, en caso de que se desarrolle software convencional, o bien clases, si se trata de software orientado a objetos. En este último caso, la prueba de unidad conlleva realizar también pruebas a nivel de método, es decir, para cada método no trivial habrá que comprobar si su comportamiento es el adecuado. Es obvio pues que para probar correctamente una clase entera, es necesario realizar pruebas con cada uno de sus métodos.

Importancia de la prueba de unidad

La prueba de unidad permite localizar errores en componentes específicos antes de integrarlos en el sistema completo, garantizando un software más fiable y facilitando la depuración temprana.
Qué son las pruebas unitarias — codigofacilito

PRUEBA DE INTEGRACIÓN

El objetivo de estas pruebas, que se realizan a continuación de las pruebas de unidad, es comprobar si las clases de las que consta el software funcionan correctamente cuando cooperan entre ellas con el objetivo de realizar las tareas encomendadas al programa.

Estrategias de integración

Existen dos estrategias principales para las pruebas de integración en sistemas orientados a objetos.
  1. Prueba basada en hebra: integra el conjunto de clases requeridas para responder a una entrada o evento del sistema. Cada hebra se integra y prueba de manera individual.
  2. Prueba basada en uso: se comienza probando las clases independientes, aquellas que usan muy pocas clases de servidor o ninguna. Después de probar estas, se examina la siguiente capa de clases dependientes, utilizadas por las primeras. Esta secuencia continúa hasta que se construye todo el sistema.
¿Qué son las Pruebas de Integración? ¿Por qué debes implementarlas? — EDteam

PRUEBA DEL SISTEMA

El proceso de prueba de un sistema integrado de hardware y software busca comprobar si cumple con los requisitos especificados, los cuales incluyen:

  • Cumplimiento de todos los requisitos funcionales, considerando el producto software final al completo, en un entorno de sistema.
  • El funcionamiento y rendimiento de las interfaces hardware, software, de usuario y de operador.
  • Adecuación de la documentación de usuario.
  • Ejecución y rendimiento en condiciones límite y de sobrecarga.

Tipos de pruebas del sistema

Es posible distinguir varios tipos de pruebas del sistema, cada una enfocada a un aspecto diferente de la calidad:
  • Pruebas de recuperación: se fuerza al software a fallar y se verifica que la recuperación se realice de manera adecuada. Se aplican a sistemas tolerantes a fallos donde la recuperación debe ser rápida.
  • Pruebas de seguridad: verifican que los mecanismos de protección del sistema protejan frente a accesos ilegales. El objetivo es evaluar cómo el sistema responde a intentos de penetración y si la dificultad de atacarlo supera los beneficios para quien lo intenta.
  • Pruebas de esfuerzo: exponen al sistema a situaciones extremas para comprobar su comportamiento ante circunstancias inusuales pero posibles en la vida real, como uso máximo de memoria o búsquedas intensivas en disco.
  • Pruebas de rendimiento: miden si el sistema cumple con los requisitos de tiempo de respuesta y eficiencia en situaciones concretas.
  • Pruebas de despliegue: verifican que la aplicación funcione correctamente en distintas plataformas y sistemas operativos, incluyendo todas las combinaciones posibles en aplicaciones web.
Pruebas de sistema - Curso de Tester de Software — Edutin Academy

PRUEBA DE VALIDACIÓN

El objetivo de la prueba de validación, también llamada prueba de aceptación, es determinar si el software es considerado válido por parte de la persona usuaria y si está preparado para su implantación en el entorno de las personas que lo vayan a usar.

Criterios de validación

La validez del software debe basarse en criterios de validación o aceptación explícitos, incluidos en la ERS (Especificación de Requisitos del Software). La ausencia de requisitos implícitos evita problemas a la hora de considerar el software listo para entrega.

Según Piattini et al. (2007), la calidad del software se puede definir como la concordancia con los requisitos explícitamente establecidos, los estándares de desarrollo fijados y los requisitos implícitos deseados por el usuario.

En este tipo de pruebas, participan activamente las personas que van a utilizar el software, ayudadas por el equipo de pruebas, ejecutando los casos necesarios. Esta es la fase final del proceso, en la que la persona usuaria da su consentimiento para su uso.

Todos estos tipos de pruebas se muestran en el modelo en V, variante del modelo en cascada.

Pruebas de validación

Pruebas de validación de Software



Técnicas de diseño de casos de prueba

Las técnicas de diseño de casos de prueba son las diferentes formas en que se pueden generar casos de prueba para probar el software. Dependiendo del enfoque que se adopte, se puede aplicar uno de los dos siguientes tipos de técnicas:

  • Pruebas de caja blanca o pruebas estructurales: examinan los detalles de cada módulo, para lo que se debe disponer del código fuente. A través de dicho código, se prueban los diferentes caminos, los bucles, las variables, etcétera.
  • Pruebas de caja negra o pruebas funcionales: se considera al software como una caja negra que recibe una serie de entradas y proporciona una serie de salidas. El objetivo de estas pruebas es validar los requisitos funcionales.

Combinación de técnicas

Lo habitual es emplear los dos tipos de técnicas de diseño de casos de prueba, pues se pueden combinar y complementar perfectamente para garantizar la calidad del software.


Pruebas de caja blanca

El objetivo de las pruebas de caja blanca es crear casos de prueba que permitan revisar detalles internos del software. Así, de estas pruebas se derivan casos de prueba que:

  • Garantizan que todas las rutas independientes dentro de un módulo se revisen al menos una vez.
  • Revisan todas las decisiones lógicas en sus lados verdadero y falso.
  • Ejecutan todos los bucles en sus fronteras y dentro de sus fronteras operativas.
  • Revisan estructuras de datos internas para garantizar su validez.

Prueba del camino básico

Esta técnica permite generar una métrica de la complejidad del módulo probado llamada complejidad ciclomática de McCabe. Es un número entero que indica el número de caminos independientes que hay en el código y, en consecuencia, el número de casos de prueba necesarios para garantizar que se recorren todas las sentencias al menos una vez.

Para calcular esta métrica es necesario representar el código en un grafo de flujo siguiendo estos pasos:

  1. Asignar un número único a cada sentencia o condición. Las instrucciones en secuencia pueden tratarse como un único bloque/nodo. Cada nodo se representa con un círculo.
  2. Unir los nodos mediante aristas (flechas) que representan el flujo de control.
Diseño de pruebas

Ejemplos de representación del grafo de flujo


La complejidad ciclomática V(G) se puede calcular de tres formas:

  • V(G) = Número de regiones del grafo (incluyendo la exterior).
  • V(G) = A - N + 2 (Aristas - Nodos + 2).
  • V(G) = NP + 1 (Nodos predicado o condiciones + 1).
Ejemplo Guiado
Fácil

Cálculo de Complejidad Ciclomática

Analiza el siguiente fragmento de código Java. Tu tarea es: 1. Identificar los nodos del grafo de flujo. 2. Identificar el nodo predicado (NP). 3. Calcular la complejidad ciclomática V(G) usando la fórmula NP + 1. public void comprobarAcceso(int edad, boolean tieneEntrada) { if (edad >= 18 && tieneEntrada) { System.out.println("Acceso permitido"); } else { System.out.println("Acceso denegado"); } }

Métrica de Complejidad

Cuando V(G) es mayor que 10, la probabilidad de defectos crece bastante. En estos casos, es recomendable dividir el módulo complejo en varios más pequeños para reducir la complejidad.
Prueba del camino básico. Pruebas de caja blanca — Juan V. Carrillo (jvprofe)

Prueba de bucles

Esta técnica se centra exclusivamente en las estructuras repetitivas. Los casos de prueba varían según el tipo de bucle:

  • Bucles simples: Para un máximo de n iteraciones, probar: saltar el bucle, una iteración, dos iteraciones, m iteraciones (m < n), y finalmente n-1, n y n+1 iteraciones.
  • Bucles anidados: Se debe comenzar por el bucle más interno (con los demás al mínimo), probarlo como bucle simple y trabajar de dentro hacia fuera manteniendo los internos en valores típicos.
  • Bucles concatenados: Si son independientes, se prueban como simples. Si son dependientes, se usa el enfoque de bucles anidados.
PRUEBA DE BUCLES (CAJA BLANCA) - PROGRAMACION 1 — Marcos Perez

Prueba de flujos de datos

Selecciona caminos de acuerdo con las ubicaciones de las definiciones y usos de variables.

  • DEF(I): Conjunto de variables que se definen (asignan valor) en la instrucción I.
  • USO(I): Conjunto de variables cuyo valor se usa en la instrucción I.

Una definición de una variable X en I está "viva" en I' si hay un camino entre ambas que no contiene ninguna otra definición de X.

Cadenas DU

Las cadenas DU tienen la forma [X, I, I'], donde X es la variable, I es donde se define e I' es donde se usa. El objetivo es generar casos de prueba que incluyan caminos para todas las cadenas DU posibles.
Ejercicio Práctico
Difícil

Ejercicio: Análisis de Flujo de Datos

En el siguiente código, debemos analizar el ciclo de vida de la variable 'x'. Identifica los conjuntos DEF y USO, y determina las cadenas DU existentes. Código numerado: 1. int x = 5; 2. int y = 10; 3. if (y > 0) { 4. x = x + y; 5. } 6. System.out.println(x); Tareas: 1. Indica en qué líneas se define la variable 'x' (DEF). 2. Indica en qué líneas se usa la variable 'x' (USO). 3. Identifica todas las cadenas DU posibles para 'x'.

Pruebas funcionales o de caja negra

El objetivo de las pruebas de caja negra es crear casos de prueba que permitan revisar todos los requisitos funcionales de un programa. A tal efecto, existen diferentes técnicas de diseño de casos de prueba de caja negra que se exponen a continuación.

Particiones o clases de equivalencia

Por medio de esta técnica, se identifican un conjunto de clases de equivalencia o tipos de valores que se deben asignar a los datos de entrada. El primer paso es determinar las condiciones de entrada del programa, que pueden ser:

  • Un valor específico.
  • Un rango de valores permitido (ejemplo: entre 18 y 65).
  • Un conjunto de valores permitidos (ejemplo: 18, 36 y 72).
  • Una condición lógica o booleana (ejemplo: es español o no lo es).

Reglas para identificar clases:

1. Valor específico: Se crea una clase válida (ese valor) y dos no válidas (valores distintos).

2. Rango de valores: Se crea una clase válida (18 ≤ número ≤ 65) y dos no válidas (número < 18 y número > 65).

3. Conjunto de valores: Si el programa los trata distinto (ej. idiomas: inglés, francés, italiano), se crea una clase válida por cada valor y una no válida (ej. alemán).

4. Condición lógica: Una clase válida (cumple la condición) y una no válida (no la cumple).

Pasos para crear casos de prueba

  1. Asignar un número único a cada clase de equivalencia.
  2. Crear el número mínimo de casos de prueba que incluyan todas las clases válidas.
  3. Crear un caso de prueba por cada clase no válida (asegurando que el resto de datos en ese caso sean válidos).
Ejercicio Práctico
Medio

Ejercicio: Clases de Equivalencia

Un programa acepta un código de descuento que debe ser un número entero entre 100 y 500 (inclusive). Identifica las clases de equivalencia y define los casos de prueba mínimos siguiendo las reglas de la unidad.

Técnica de Partición de Equivalencia | Ejercicio Práctico | Caja Negra | ISTQB — Full Advanced

Análisis de valores límite

Es una técnica complementaria a las clases de equivalencia. Sus diferencias principales son:

  • Se seleccionan los valores que están justo en los límites de la clase.
  • Se generan casos teniendo en cuenta tanto las entradas como el dominio de salida.

Reglas de generación:

Entrada Rango: Si el rango es -5 a 10, probamos los extremos (-5, 10) y justo fuera (-5.01, 10.01).

Número de valores: Si es de 1 a 100, probamos 1 y 100 (mín/máx), y los adyacentes 0 y 101.

Salida: Si el salario debe ser entre 1200,00 € y 3600,00 €, forzamos casos que den exactamente esos resultados y los adyacentes (1199,99 € y 3600,01 €).

Conjuntos ordenados: Si es una tabla o archivo, centrarse siempre en el primer y último elemento.

Técnica de Análisis de Valores Frontera ó Límites | Ejercicio Práctico | Caja Negra ISTQB — Full Advanced

Conjetura de errores

Consiste en generar una lista de errores que suelen cometer quienes desarrollan aplicaciones, basándose en la experiencia y situaciones comunes. Ejemplos típicos:

  • El valor 0 tanto en la entrada como en la salida.
  • Listas vacías, con un solo valor o con todos los valores iguales.
  • Introducción de caracteres no numéricos en campos de número.
  • Interpretaciones incorrectas de la especificación por parte del programador.

Estrategia de aplicación

El proceso de prueba de una aplicación completa debe seguir este orden lógico:

1. Prueba de Unidad

Se comienza con cada clase individualmente. Se aplica Análisis de Valores Límite, se añaden clases de equivalencia y se finaliza con Conjetura de Errores. Si no se alcanza la cobertura deseada del código, se aplican técnicas de Caja Blanca (camino básico, bucles, flujo de datos).

2. Prueba de Integración

Se centra en la interacción entre clases usando preferentemente técnicas de caja blanca. Se incrementa el alcance hasta abarcar todo el software.

3. Prueba del Sistema

Se emplean casos de prueba de caja negra para verificar el cumplimiento de los objetivos globales requeridos.

4. Prueba de Validación

Participan los usuarios finales. Se pueden reutilizar casos del sistema. Su éxito supone la aceptación final del software.



Documentación de las pruebas

La documentación de las pruebas es conveniente para una buena organización de estas y para asegurar su reutilización. Se deben documentar tanto el diseño de las pruebas como el resultado o ejecución de las mismas.

Documentación del diseño de las pruebas

En cuanto a la documentación del diseño de las pruebas, es conveniente crear los siguientes documentos:

Plan de pruebas

Es un documento con la planificación general de las pruebas para la aplicación que se está creando. En él se debe indicar el enfoque general de las pruebas, los recursos requeridos, las actividades de pruebas que se van a llevar a cabo, los elementos que se deben probar, el personal responsable y los riesgos asociados.

Especificación del diseño de las pruebas

Es un informe que detalla el plan de pruebas porque en él se indica cada uno de los elementos concretos que se van a probar y los procedimientos de prueba. A partir de este documento se generan los siguientes:

  • Especificaciones de casos de prueba:

    Por cada prueba que se va a realizar, se deben indicar los datos de entrada que se van a suministrar, los resultados esperados y las dependencias entre casos de prueba.

  • Especificaciones de procedimientos de prueba:

    Especifica los pasos para la ejecución de uno o de un conjunto de casos de prueba. Se debe indicar la lista de casos de prueba correspondientes al procedimiento, los requisitos para la ejecución (entorno, personal, etc.) y los pasos del procedimiento.

Documentación de la ejecución de las pruebas

Esta documentación es importante para la eficacia en la detección y corrección de defectos y para dejar constancia de los resultados. La ejecución toma como entrada las especificaciones de los casos y procedimientos de prueba, y debe generar dos tipos de documentos:

Histórico de pruebas

Registra todos los hechos relevantes ocurridos durante la ejecución: elementos probados, entorno de pruebas, resultados obtenidos, fecha y hora y referencia al informe de incidentes, si es el caso.

Informe de incidentes

Registra cada incidente ocurrido durante la prueba que requiera una posterior investigación.

La ejecución de las pruebas correspondiente a cada especificación del diseño genera asimismo un informe resumen de pruebas, que incluye:

  • Resumen de la evaluación de los elementos probados.
  • Variaciones del software como resultado de las pruebas.
  • Valoración de la cobertura de la prueba.
  • Resumen de los resultados obtenidos.
  • Resumen de las actividades (incluyendo consumo de recursos).
Cómo documentar pruebas de software - Curso Tester de Software — Edutin Academy

Análisis causal de los defectos

La valoración de los procesos de prueba es importante porque puede ayudar en proyectos posteriores. Por ello, es conveniente realizar un análisis causal, cuyo objetivo es proporcionar información sobre la naturaleza de los defectos encontrados.

Por cada defecto encontrado, se debería registrar la siguiente información:

¿Cuándo se cometió?
¿Quién lo cometió?
¿Qué se hizo mal?
¿Cómo se podría haber evitado?
¿Por qué no se detectó antes?
¿Cómo se podría haber detectado antes?
¿Cómo se encontró el error?

Finalidad del Análisis Causal

El objetivo es formar al personal sobre los errores cometidos para que se puedan prevenir en el futuro. La información generada se puede emplear para predecir los fallos futuros del software.
Ejemplo Guiado
Fácil

Práctica de Documentación: Análisis Causal

Imagina que un desarrollador olvidó cerrar una conexión a la base de datos, lo que provocó que el sistema se bloqueara después de 10 minutos de uso. El error se encontró en la prueba de sistema. Completa los puntos clave del análisis causal para este error siguiendo el temario.



Depuración (Debugging) y Pruebas de Regresión

La depuración es la tarea que hay que realizar tras una prueba exitosa, es decir, tras una prueba que detecta un fallo o, más bien, el síntoma de un defecto. En ese caso, lo que se hace es localizar el defecto en el software y corregirlo.

A veces, la localización del defecto es una tarea sencilla, pero otras veces no tanto. Para ello, resultan fundamentales las herramientas de depuración (debuggers) que proporcionan los entornos de desarrollo modernos. En ocasiones, para detectar el defecto real oculto tras un síntoma, puede ser necesario crear nuevos casos de prueba específicos.

Pruebas de Regresión

Al corregir un defecto, se pueden introducir nuevos errores de forma inadvertida. Por ello, es imperativo realizar pruebas de regresión: consisten en repetir los casos de prueba que se ejecutaron antes de las modificaciones para asegurar que los cambios no han dañado funcionalidades que antes funcionaban correctamente.

Anatomía del Depurador en IntelliJ IDEA

Cuando activas el modo debug (usando el icono del bicho 🐞), IntelliJ abre una ventana especializada. Entender sus paneles es la diferencia entre perderse o encontrar el error rápido:

Panel de Frames (Pila de llamadas)

Muestra la "escalera" de métodos. Si el error está en la línea 50, puedes ver qué método llamó a ese y con qué valores. Es como viajar al pasado de la ejecución.

Panel de Variables

Aquí ves el valor real de cada variable en ese instante preciso. Truco: Puedes hacer clic derecho en un valor y elegir "Set Value" para cambiarlo en pleno vuelo y ver qué pasaría.

Evaluate Expression (Alt + F8)

Es una calculadora mágica. Puedes escribir cualquier código Java (ej: lista.size() * 2) y el IDE te dará el resultado usando los datos reales del programa pausado.

Comandos de Navegación

Para controlar el avance del programa mientras está pausado, usamos:

ComandoFunción
Step Over (F8)Ejecuta la línea actual y pasa a la siguiente sin entrar en métodos internos.
Step Into (F7)Entra dentro del método que se encuentra en la línea actual.
Step Out (Shift+F8)Termina la ejecución del método actual y vuelve al llamador.
Resume Program (F9)Continúa la ejecución normal hasta el siguiente breakpoint.
Ejercicio Práctico
Medio

Simulación de Depuración Paso a Paso

Se tiene el siguiente código para calcular la media de un array, pero devuelve NaN si el array está vacío. 1. double suma = 0; 2. for (double n : numeros) { 3. suma += n; 4. } 5. double media = suma / numeros.length; Si ponemos un breakpoint en la línea 1 y usamos 'Step Over' 3 veces en un array de 2 elementos, ¿en qué línea estaremos y qué valor tendrá 'suma'?

Debug Java Like a Pro in IntelliJ IDEA — Tom Gregory


Pruebas Automáticas con JUnit 5

Las pruebas de unidad o unitarias son aquellas que aíslan y verifican la unidad más pequeña de código comprobable en una aplicación: la clase y sus métodos. Tradicionalmente, los estudiantes prueban su código ejecutando el método main e imprimiendo resultados por consola (System.out.println). Sin embargo, este enfoque manual presenta graves problemas en el mundo profesional:

  • No son escalables: A medida que el proyecto crece a miles de clases, es imposible probar todo manualmente antes de cada entrega.
  • Error humano: Dependen de que el desarrollador mire la consola y juzgue si el resultado es correcto o no.
  • Efímeras: Una vez ejecutadas, no queda un registro formal de que el sistema funcionaba correctamente en ese momento.

Para solucionar esto, utilizamos JUnit 5, el marco de trabajo (framework) estándar en la industria Java. JUnit nos permite escribir código que prueba nuestro código. Estas pruebas se convierten en un activo del proyecto: una "red de seguridad" que se ejecuta en milisegundos y nos alerta inmediatamente si un cambio reciente ha roto una funcionalidad existente (Pruebas de Regresión).

El Sujeto de Prueba: Clase Cuenta

Para entender JUnit, necesitamos una clase con "reglas de negocio". Utilizaremos una clase Cuenta que simula una cuenta bancaria. Observa que los métodos ingresar y extraer tienen validaciones (if) que lanzan excepciones. Estas validaciones son precisamente lo que debemos probar.

java
1public class Cuenta {
2    private String numero;
3    private double saldo;
4
5    public Cuenta(String numero, double saldo) {
6        this.numero = numero;
7        this.saldo = saldo;
8    }
9
10    public void ingresarDinero(double importe) {
11        if (importe <= 0) {
12            throw new IllegalArgumentException("El importe a ingresar debe ser positivo");
13        }
14        this.saldo += importe;
15    }
16
17    public void extraerDinero(double importe) {
18        if (importe <= 0) {
19            throw new IllegalArgumentException("El importe a extraer debe ser positivo");
20        }
21        if (importe > saldo) {
22            // Protección: No permitimos números rojos.
23            // En un caso real, podríamos lanzar InsufficientFundsException
24            return; 
25        }
26        this.saldo -= importe;
27    }
28
29    public double getSaldo() { return saldo; }
30    public String getNumero() { return numero; }
31}

Ciclo de Vida del Test (Lifecycle)

Una de las reglas de oro de las pruebas unitarias es la Independencia. El resultado de la prueba A nunca debe depender de si la prueba B se ejecutó antes o después. Si la prueba A retira dinero de la cuenta, la prueba B no debería encontrarse la cuenta vacía.

Para garantizar esto, JUnit crea una nueva instancia de la clase de test para cada método de prueba. Además, nos ofrece anotaciones para controlar qué ocurre antes y después de cada ejecución.

@BeforeEach (Configuración)

Este método se ejecuta antes de CADA test individual. Es el lugar obligatorio para inicializar los objetos.

@BeforeEach
void setUp() {
  cuenta = new Cuenta("ES01", 1000.0); // Reiniciamos la cuenta a 1000€ siempre
}

@BeforeAll y @AfterAll (Globales)

Se ejecutan una sola vez por toda la clase. Son útiles para operaciones "caras" como abrir una conexión a base de datos. Nota importante: Deben ser métodos estáticos (static) porque se ejecutan antes de que se instancie la clase de test.

Aserciones: Verificando la Realidad

Un test sin aserciones no sirve de nada. La aserción es la sentencia que valida si el resultado obtenido es igual al esperado. JUnit 5 proporciona la clase estática Assertions con múltiples métodos. Todos siguen la lógica: "Espero X, pero he recibido Y".

MétodoUso Común
assertEquals(esperado, actual)El más usado. Verifica igualdad exacta (números, strings).
assertTrue(condición)Verifica que una condición booleana sea verdadera.
assertNull(objeto)Verifica que una variable no apunte a ningún objeto.
assertArrayEquals(arr1, arr2)Compara dos arrays elemento por elemento.
15 - Aserciones, pruebas unitarias en JUnit y depuración — Javi López

Tratamiento de Excepciones

Un error común en principiantes es pensar que si el código lanza una excepción, el test ha fallado. ¡Al contrario! Si diseñamos un sistema robusto, queremos que lance excepciones cuando alguien usa mal el sistema.

Probando el camino infeliz

Si un usuario intenta ingresar -100€, el sistema DEBE fallar. Si no falla (y se traga el error o resta dinero), entonces tenemos un bug. JUnit usa assertThrows para asegurar que el sistema se queja cuando debe.

Sintaxis Avanzada con Lambdas

El método assertThrows utiliza una función lambda (() -> código) para envolver la llamada que esperamos que falle. Además, devuelve la excepción capturada para que podamos inspeccionar su mensaje.

java
1@Test
2@DisplayName("Ingreso negativo lanza excepción controlada")
3void testIngresoNegativo() {
4    // 1. Ejecutamos y capturamos
5    Exception excepcionCapturada = assertThrows(IllegalArgumentException.class, () -> {
6        cuenta.ingresarDinero(-500.0);
7    });
8
9    // 2. Inspeccionamos el mensaje
10    String mensajeEsperado = "El importe a ingresar debe ser positivo";
11    String mensajeReal = excepcionCapturada.getMessage();
12
13    // 3. Validamos que el mensaje sea útil para el usuario
14    assertEquals(mensajeEsperado, mensajeReal, "El mensaje de error no es el correcto");
15}
Cómo hacer tests de excepciones en JUnit 5 con assertThrows — makigas

Pruebas Parametrizadas

A veces queremos probar la misma lógica con muchos valores distintos (ej: ingresar 10, 50, 100, 5000). Escribir 4 métodos `@Test` copiando y pegando código es una mala práctica (viola el principio DRY - Don't Repeat Yourself).

JUnit 5 ofrece @ParameterizedTest y @ValueSource para ejecutar el mismo test múltiples veces con diferentes argumentos.

java
1@ParameterizedTest
2@ValueSource(doubles = { 10.0, 50.5, 100.0, 2000.0 })
3@DisplayName("Ingresar varias cantidades válidas")
4void testVariosIngresos(double cantidad) {
5    double saldoInicial = cuenta.getSaldo();
6    cuenta.ingresarDinero(cantidad);
7    assertEquals(saldoInicial + cantidad, cuenta.getSaldo());
8}

Este único método se ejecutará 4 veces, una por cada número en @ValueSource. En la consola de IntelliJ veremos 4 checks verdes.

Análisis de Cobertura (Code Coverage)

¿Cómo sabemos si hemos probado todo? Aquí entra el Análisis de Cobertura. Es una métrica porcentual que indica cuánto código de nuestra aplicación se ha ejecutado durante los tests. IntelliJ IDEA pinta el margen izquierdo del editor (gutter) con colores semáforo.

Verde (Full Coverage)

La línea se ha ejecutado al menos una vez. ¡Bien!

Rojo (No Coverage)

Código muerto para los tests. Si hay un error aquí, nadie se enterará hasta que explote en producción. Peligro alto.

Amarillo (Partial Coverage - Ramas)

Este es el más sutil y peligroso. Ocurre en estructuras condicionales (if, switch). Significa que has probado una parte de la condición pero no la otra.

Ejemplo: Tienes if (saldo &gt 0). Has hecho un test con saldo 100 (entra en el if), pero no has hecho un test con saldo 0 o negativo (no entra). Tu cobertura de ramas está al 50%.

Java: Pruebas Unitarias y Cobertura de Código | Tutorial Completo y Fácil — Programando en JAVA
Ejercicio Práctico
Difícil

Ejercicio Integrador de Cobertura

Observa el siguiente método: public double calcularDescuento(int edad, boolean socio) { if (edad > 65 || socio) { return 0.15; // 15% descuento } return 0.0; } ¿Cuántos casos de prueba mínimos necesitas para obtener el 100% de cobertura de ramas y por qué?

¿Has encontrado un error, algo desactualizado o una explicación que no se entiende? Avísanos y lo corregimos.

¿Has terminado este tema?

Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

Guardar mi progreso