-
Notifications
You must be signed in to change notification settings - Fork 3
06. JUnit, Maven y Mockito
Permiten examinar la funcionalidad de métodos o clases de manera aislada, sin tener en cuenta sus dependencias. Garantizando que cada componente trabaje correctamente y cumpla con sus objetivos específicos.
JUnit es un framework popular para realizar pruebas unitarias en Java.
Estructura de una clase de prueba:
package test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import src.Ejercicio1;
public class Ejercicio1Test {
@Test
public void testMetodo() {
// Ejecuta el método a probar
Integer resultado = Ejercicio1.metodo(2);
// Verifica si el resultado es el esperado
Assertions.assertEquals(4, resultado);
}
}La clase
Ejercicio1Testverifica el funcionamiento del métodometodo()de la claseEjercicio1. El método de prueba está anotado con@Test, indicando que debe ejecutarse como una prueba. Se utilizaAssertions.assertEquals()para comparar el resultado del método con el valor esperado; si coinciden, la prueba pasa, y si no, JUnit informa el fallo, detallando la diferencia. Esta estructura permite validar que el código funciona correctamente sin necesidad de un métodomain.
Las convenciones de nomenclatura son reglas que facilitan la legibilidad, colaboración y estructura del código en proyectos. A continuación, se detallan las más comunes:
-
Regla: Agregar
Testal final del nombre de la clase principal. -
Ejemplo:
Calculadora→CalculadoraTest.
package test;
public class CalculadoraTest {
// pruebas unitarias para Calculadora
}-
testMethodName: Simple y antigua.
@Test
void testSuma() {
// código de prueba aquí
}-
should_MethodName_ExpectedBehavior_GivenCondition: Descriptiva. Formato should-when-given.
@Test
void should_Suma_ReturnCorrectSum_GivenMultipleNumberPairs() {
// código de prueba aquí
}-
MethodName_GivenCondition_ExpectedBehavior: Condición antes del resultado. Leer como secuencia causa y efecto.
@Test
void suma_GivenMultipleNumberPairs_ReturnsCorrectSum() {
// código de prueba aquí
}-
given_Precondition_When_StateUnderTest_Then_ExpectedBehavior: Enfoque Given-When-Then. Muy descriptiva.
@Test
void given_MultipleNumberPairs_When_SumaCalled_Then_ReturnsCorrectSum() {
// código de prueba aquí
}-
MethodName_Scenario_ExpectedResult: Útil para pruebas con diferentes escenarios. Prueba lo que prueba
@Test
void suma_MultipleNumberPairs_CorrectSum() {
// código de prueba aquí
}(JUnit 5)
Permite nombres descriptivos en pruebas. Útil para asignar un nombre más descriptivo.
@Test
@DisplayName("Suma de múltiples pares de números: retorna suma correcta")
void testSuma() {
// código de prueba aquí
}Las aserciones (afirmaciones), son declaraciones que verifican si cierta condición es verdadera.
JUnit 5 proporciona la clase org.junit.jupiter.api.Assertions, que incluye una variedad de métodos estáticos para realizar diferentes tipos de afirmaciones:
Ejemplos comunes:
-
assertEquals(expected, actual)
Verifica si dos valores son iguales. Si no lo son, la prueba fallará.@Test void testSuma() { // La suma debería ser 5 assertEquals(5, 2 + 3); }
-
assertNotEquals(expected, actual)
Verifica si dos valores NO son iguales. Si lo son, la prueba fallará.@Test void testSuma() { // La suma no debería ser 6 assertNotEquals(6, 2 + 3); }
-
assertTrue(condition)
Verifica si una condición es verdadera. Si no lo es, la prueba fallará.@Test void testIsEven() { // 4 debería ser par assertTrue(4 % 2 == 0); }
-
assertFalse(condition)
Verifica si una condición es falsa. Si no lo es, la prueba fallará.@Test void testIsOdd() { // 4 no debería ser impar assertFalse(4 % 2 != 0); }
-
assertNull(value)
Verifica si un valor es nulo. Si no lo es, la prueba fallará.@Test void testNullValue() { String str = null; // La variable debería ser nula assertNull(str); }
-
assertNotNull(value)
Verifica si un valor NO es nulo. Si lo es, la prueba fallará.@Test void testNotNullValue() { String str = "Hola mundo"; // La variable no debería ser nula assertNotNull(str); }
-
assertSame(expected, actual)
Verifica si dos referencias de objetos apuntan al mismo objeto. Si no lo hacen, la prueba fallará.@Test void testSameObject() { String str1 = "Hola mundo"; String str2 = str1; // Las variables deberían referenciar al mismo objeto assertSame(str1, str2); }
-
assertNotSame(expected, actual)
Verifica si dos referencias de objetos NO apuntan al mismo objeto. Si lo hacen, la prueba fallará.@Test void testNotSameObject() { String str1 = new String("Hola mundo"); String str2 = new String("Hola mundo"); // Las variables no deberían referenciar al mismo objeto. assertNotSame(str1, str2); }
-
assertArrayEquals(expectedArray, actualArray)
Verifica si dos arrays son iguales. Si no lo son, la prueba fallará.@Test void testArrayEquality() { int[] array1 = {1, 2, 3}; int[] array2 = {1, 2, 3}; // Los arrays deberían ser iguales assertArrayEquals(array1, array2); }
-
assertThrows(expectedType, executable)
Verifica si una operación lanza una excepción del tipo esperado.@Test void testException() { // Debería lanzar ArithmeticException assertThrows(ArithmeticException.class, () -> { int division = 5 / 0; }); }
-
assertEquals(double expected, double actual, double delta)
Compara números de punto flotante permitiendo una diferencia de precisión.@Test public void testSquareRoot() { // El valor de la raíz cuadrada de 4 debería ser 2 assertEquals(2.0, Math.sqrt(4.0)); // La raíz cuadrada de 2 debería ser cercana a 1.4142 assertEquals(1.4142, Math.sqrt(2.0), 0.0001); }
Note
Todos los métodos mencionados anteriormente también tienen una sobrecarga que acepta un parámetro adicional de tipo String, lo que permite agregar un mensaje personalizado que se imprimirá en caso de que la afirmación falle.
@Test
void testSuma() {
assertEquals(5, 2 + 3, "La suma debería ser 5");
}Esta estructura es comúnmente utilizada para organizar y escribir pruebas unitarias.
- Arrange: Configura el entorno.
- Act: Ejecuta el método bajo prueba.
- Assert: Verifica resultados esperados.
@Test
void testSuma() {
// Arrange
int numero1 = 4;
int numero2 = 5;
// Act
int resultado = Calculadora.suma(numero1, numero2);
// Assert
assertEquals(9, resultado, "La suma de 4 y 5 debería ser 9");
}En el ejemplo, se puede observar lo siguiente:
- Arrange: Se definen dos números que serán sumados.
- Act: Se llama al método estático suma() de la clase Calculadora, pasando los dos números como argumentos.
- Assert: Se verifica que el resultado de la suma sea igual a 9. En caso contrario, la prueba fallará y se mostrará el mensaje "La suma de 4 y 5 debería ser 9".
El ciclo de vida de las pruebas unitarias se refiere al proceso y los pasos que JUnit sigue cuando se ejecutan los tests de una clase. Estos pasos pueden ser personalizados mediante el uso de anotaciones específicas:
Controla la configuración y limpieza antes o después de las pruebas. Anotaciones clave:
-
@BeforeAll: Ejecuta antes de todas las pruebas (método estático). -
@BeforeEach: Ejecuta antes de cada prueba. -
@Test: Declara un método de prueba. -
@AfterEach: Ejecuta después de cada prueba. -
@AfterAll: Ejecuta después de todas las pruebas (método estático).
import org.junit.jupiter.api.*;
class MyTestClass {
@BeforeAll
static void initAll() {
// Código para configurar el estado antes de todas las pruebas
System.out.println("Configuración inicial antes de todas las pruebas");
}
@BeforeEach
void setUp() {
// Código para configurar antes de cada prueba
System.out.println("Configuración antes de cada prueba");
}
@Test
void myTest() {
// Código de la prueba
System.out.println("Ejecución de la prueba");
Assertions.assertEquals(2, 1 + 1); // Ejemplo de una aserción
}
@AfterEach
void tearDown() {
// Código para limpiar el estado después de cada prueba
System.out.println("Limpieza después de cada prueba");
}
@AfterAll
static void tearDownAll() {
// Código para limpiar el estado después de todas las pruebas
System.out.println("Limpieza final después de todas las pruebas");
}
}Estas anotaciones simplifican el manejo del ciclo de vida de las pruebas, permitiendo configurar y limpiar estados de forma eficiente. Ayudan a reducir código repetitivo y mejoran la legibilidad y mantenibilidad de las pruebas.
Las pruebas parametrizadas en JUnit permiten ejecutar una misma prueba con diferentes datos de entrada, evitando duplicar código. Son ideales para lógica repetitiva, mejorando la legibilidad y mantenimiento de las pruebas.
Para crearla:
- Se utiliza la anotación
@ParameterizedTest. - Se proporcionan los datos de entrada utilizando una de las siguientes anotaciones de origen de datos:
@ValueSource,@MethodSource,@CsvSource,@CsvFileSource, entre otras.
Permite definir un conjunto de valores literales de un único tipo como argumentos para las pruebas.
@ParameterizedTest
@ValueSource(ints = {2, 3, 5, 7, 11, 13, 17, 19})
void testEsPrimo(int number) {
assertTrue(Primos.esPrimo(number), "El número debería ser primo");
}Utiliza un método para proporcionar parámetros a las pruebas. El método debe ser estático y no tener parámetros.
@ParameterizedTest
@MethodSource("provideStrings")
void testWithExplicitLocalMethodSource(String argument) {
assertNotNull(argument);
}
static String[] provideStrings() {
return new String[] {"manzana", "banana"};
}Define parámetros para la prueba en formato CSV directamente en la anotación.
@ParameterizedTest
@CsvSource({"1, 2, 3", "2, 3, 5", "3, 5, 8"})
void testSuma(int num1, int num2, int expectedResult) {
assertEquals(expectedResult, Calculadora.suma(num1, num2), "La suma debería ser correcta");
}Lee parámetros desde un archivo CSV externo.
@ParameterizedTest
@CsvFileSource(resources = "/test/day-week-data.csv", numLinesToSkip = 1) // Archivo day-week-data.csv
void testWithCsvFileSource(String dayWeek, Integer expectedResult) {
Integer result = getNumberDay(dayWeek);
assertEquals(expectedResult, result);
}JUnit 5 permite ejecutar automáticamente una prueba varias veces utilizando la anotación @RepeatedTest. Es útil para pruebas no deterministas o con datos aleatorios.
La prueba se repetirá 5 veces.
class MyTestClass {
@RepeatedTest(5)
void myRepeatedTest() {
// Código de la prueba aquí...
}
}RepetitionInfo permite acceder a la repetición actual y al número total de repeticiones.
class MyTestClass {
@RepeatedTest(5)
void myRepeatedTest(RepetitionInfo repetitionInfo) {
int currentRepetition = repetitionInfo.getCurrentRepetition(); // Número actual de repetición
int totalRepetitions = repetitionInfo.getTotalRepetitions(); // Total de repeticiones
// Código de la prueba aquí...
}
}-
getCurrentRepetition(): Devuelve el número de la repetición actual. -
getTotalRepetitions(): Devuelve el total de repeticiones configuradas.
Herramienta de gestión de proyectos que simplifica y automatiza el proceso de construccción, dependencias y despliegue de aplicaciones.
Está diseñada para manejar el ciclo de vida completo de un proyecto de software, desde la etapa de compilación hasta la etapa de prueba, empaquetado, y despliegue.
Al crear un proyecto con Maven, se genera una estructura específica de directorios que organiza los archivos del proyecto:

-
pom.xml: Archivo principal de configuración. Define dependencias, plugins, y perfiles de construcción del proyecto. -
src/main/java/com/my-company/app/: Carpeta para el código fuente de la aplicación (reemplazarmy-companyyappcon los nombres adecuados). -
src/main/resources: Almacena recursos de la aplicación, como archivos de configuración y propiedades. -
src/test/java/com/my-company/app/: Carpeta para el código fuente de las pruebas unitarias. -
src/test/resources: Almacena recursos para las pruebas, como archivos de configuración o datos de prueba (por ejemplo, archivos CSV). -
target/: Carpeta generada automáticamente para los archivos de compilación y salidas creadas por plugins de Maven. -
target/classes: Contiene los archivos.classcompilados tras la construcción del proyecto.
El archivo pom.xml que compartiste es una parte esencial de un proyecto Maven. Aquí tienes un resumen de su contenido y propósito:
El archivo pom.xml (Project Object Model) es un archivo de configuración para proyectos Maven que sigue el enfoque de Convención sobre Configuración. Esto significa que:
- Proporciona una estructura estándar para todos los proyectos.
- Reduce la necesidad de configuraciones personalizadas.
- Aumenta la productividad y la interoperabilidad con herramientas y sistemas externos.
-
Raíz del proyecto
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- Define el espacio de nombres y la versión del esquema XML.
- Garantiza que el archivo sigue un formato válido.
-
Versión del modelo
<modelVersion>4.0.0</modelVersion>
- Especifica la versión del modelo POM (siempre es
4.0.0en Maven moderno).
- Especifica la versión del modelo POM (siempre es
-
Identificación del proyecto
<groupId>com.mycompany</groupId> <artifactId>app</artifactId> <version>1.0-SNAPSHOT</version>
-
<groupId>: Identifica la organización o grupo. Ejemplo:com.empresa. -
<artifactId>: Nombre único del proyecto. Ejemplo:app. -
<version>: Versión del proyecto. Ejemplo:1.0-SNAPSHOT.
-
-
Propiedades reutilizables
<properties> <java.version>20</java.version> <maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.target>${java.version}</maven.compiler.target> </properties>
- Facilita el uso de variables reutilizables, como la versión de Java.
-
Dependencias
<dependencies> <!-- Aquí puedes agregar las bibliotecas necesarias --> </dependencies>
- Especifica bibliotecas externas requeridas usando
<groupId>,<artifactId>y<version>.
- Especifica bibliotecas externas requeridas usando
-
Configuración de construcción
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> </plugins> </build>
-
<build>: Define cómo se debe compilar y construir el proyecto. -
<plugins>: Especifica plugins adicionales para tareas específicas, como compilación.
-
- Menos configuración personalizada: Uso de convenciones predeterminadas.
- Estandarización: Estructura consistente en todos los proyectos.
- Interoperabilidad: Compatible con diversas herramientas y sistemas.
- Aumento de productividad: Configuración automática y más tiempo para el desarrollo.
Las dependencias en Maven son fundamentales para gestionar bibliotecas externas necesarias en un proyecto. Maven automatiza la descarga e integración de estas dependencias, asegurando un flujo de trabajo eficiente y organizado. Maven gestiona las dependencias al declararlas en el archivo pom.xml:
Ventajas:
- Automatización: No se necesita descargar manualmente las librerías.
- Colaboración: Otros desarrolladores pueden ejecutar el proyecto sin buscar las dependencias, ya que Maven las descarga automáticamente.
- Limpieza: Las dependencias se almacenan fuera del directorio del proyecto, manteniéndolo ligero y organizado.
Para incluir JUnit 5:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
</dependencies>- Central: Maven busca dependencias en el repositorio central por defecto.
-
Local: Descargas se guardan en
.m2/repository(en el directorio de usuario) para reutilización. - Personalizados: Se pueden configurar repositorios adicionales si es necesario.
Note
- Maven también gestiona las dependencias de tus dependencias. Por ejemplo: Si
Adepende deB, yBdepende deC, entoncesCes una dependencia transitiva deA.
El alcance (scope) indica en qué etapas del ciclo de vida de Maven se utilizará la dependencia:
-
compile: Por defecto, necesaria en todas las fases. -
test: Solo para pruebas. -
runtime: Necesaria en ejecución, no en compilación. -
provided: Necesaria en compilación, pero se espera que esté disponible en tiempo de ejecución (por ejemplo, bibliotecas del servidor). -
system: Similar aprovided, pero se debe especificar la ubicación del archivo JAR manualmente.
Maven tiene tres ciclos principales:
Fases principales:
-
validate: Verifica que el proyecto es válido. -
compile: Compila el código fuente. -
test: Ejecuta pruebas unitarias. -
package: Empaqueta el proyecto en un JAR o WAR. -
verify: Verificaciones de control de calidad. -
install: Instala el paquete en el repositorio local. -
deploy: Publica el paquete en un repositorio remoto.
Nota: Ejecutar una fase también ejecuta las anteriores. Por ejemplo,
mvn installejecutarávalidate,compile,test, etc.
-
clean: Elimina los archivos generados (carpetatarget).
-
site: Genera documentación entarget/site.
- Plugins: Añaden funcionalidad específica al ciclo de vida. Ejemplo: compilación, ejecución de pruebas.
-
Goals: Son tareas individuales que ejecuta un plugin. Ejemplo:
mvn compileejecuta el goalcompiledel plugin del compilador.
Note
Comandos comunes:
-
Compilar:
mvn compile -
Empaquetar:
mvn package -
Instalar localmente:
mvn install -
Limpiar y compilar:
mvn clean compile
-
targetes el directorio donde Maven coloca los archivos generados al construir un proyecto.-
mvn compile: Los archivos.classse ubican entarget/classes. -
mvn package: El archivo empaquetado resultante (JAR, WAR o EAR) se coloca entarget.
-
- JAR (Java Archive): Archivo ejecutable de Java. Necesita JRE para ejecutarse.
- WAR (Web Application Archive): Contiene aplicaciones web Java y se despliega en servidores.
- EAR (Enterprise Archive): Similar al WAR, pero para aplicaciones empresariales más complejas.
Para cambiar el tipo de paquete generado, configura <packaging> en el archivo pom.xml:
<packaging>war</packaging>- Los arquetipos son plantillas para crear proyectos con una estructura predeterminada.
- Ejemplo común:
maven-archetype-quickstart, que incluye una clase principal y pruebas.
Comando para generar un proyecto desde un arquetipo:
mvn archetype:generate \
-DgroupId=com.mycompany.app \
-DartifactId=my-app \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false-
DgroupId: Identificador del grupo (dominio inverso). -
DartifactId: Nombre del proyecto. -
DarchetypeArtifactId: Identificador del arquetipo.
-
Crear un proyecto base: Define la estructura y configuración inicial.
-
Descriptor del arquetipo: Archivo
archetype-metadata.xmlque indica qué incluir.<archetype-descriptor name="myArchetype"> <fileSets> <fileSet filtered="true" packaged="true"> <directory>src/main/java</directory> <includes> <include>**/*.java</include> </includes> </fileSet> </fileSets> </archetype-descriptor>
-
Configurar el plugin en el
pom.xml:<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-archetype-plugin</artifactId> <version>3.2.0</version> </plugin> </plugins> </build>
-
Generar el arquetipo:
-
mvn archetype:create-from-project: Crea el arquetipo entarget/generated-sources/archetype. -
mvn installomvn deploy: Instala el arquetipo en el repositorio local o remoto.
-
-
Usar con
mvn archetype:generateespecificandogroupId,artifactIdy versión del arquetipo.
Mockito es un framework de simulación (mocking) en Java ampliamente utilizado para desarrollar pruebas unitarias. Facilita la creación de objetos simulados, configuración de comportamientos, y la verificación de interacciones en pruebas de manera sencilla y flexible.
Para proyectos con Maven, agregar la siguiente dependencia al archivo pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>-
Creación de objetos simulados (mocks)
Los objetos simulados actúan como sustitutos de objetos reales. Utiliza el métodomock().List<String> mockedList = mock(List.class);
-
Configuración de comportamientos
Define qué debe hacer un objeto simulado al llamar métodos específicos conwhen().thenReturn().when(mockedList.get(0)).thenReturn("primer elemento");
-
Verificación de interacciones
Comprueba si un método se ha llamado, cuántas veces, y con qué argumentos.verify(mockedList).add("un elemento");
-
Captura de argumentos
Permite verificar los valores pasados a métodos del objeto simulado conArgumentCaptor.ArgumentCaptor<String> argCaptor = ArgumentCaptor.forClass(String.class); verify(mockedList).add(argCaptor.capture()); assertEquals("un elemento", argCaptor.getValue());
-
Creación de objetos espías (spies)
Los espías rastrean y modifican el comportamiento de un objeto real, manteniendo su implementación original.List<String> spyList = spy(new ArrayList<>()); spyList.add("elemento real"); verify(spyList).add("elemento real");
-
Simulación de dependencias
Mockito permite simular dependencias externas para aislar la unidad que se prueba.PaymentGateway mockPaymentGateway = mock(PaymentGateway.class); when(mockPaymentGateway.processPayment(100.0)).thenReturn(true); OrderProcessor orderProcessor = new OrderProcessor(mockPaymentGateway); assertTrue(orderProcessor.processOrder(100.0)); verify(mockPaymentGateway).processPayment(100.0);
Ejemplo: Simulación de dependencias
Clase real:
public class OrderProcessor {
private PaymentGateway paymentGateway;
public OrderProcessor(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public boolean processOrder(double amount) {
return paymentGateway.processPayment(amount);
}
}Prueba:
@Test
public void testOrderProcessor() {
PaymentGateway mockPaymentGateway = mock(PaymentGateway.class);
when(mockPaymentGateway.processPayment(100.0)).thenReturn(true);
OrderProcessor orderProcessor = new OrderProcessor(mockPaymentGateway);
assertTrue(orderProcessor.processOrder(100.0));
verify(mockPaymentGateway).processPayment(100.0);
}- Pruebas más rápidas y fiables.
- Aislamiento de dependencias externas.
- Flexibilidad para configurar y verificar el comportamiento de objetos.