Saltar al contenido principal

Ejercicios y taller de calidad

En este taller recibirás un pequeño proyecto que «funciona en mi máquina», pero no ofrece una forma fiable de comprobarlo. Lo convertiremos paso a paso en un proyecto reproducible.

Usaremos un gestor de inventario local. Cada ejercicio incluye situación, objetivo, criterio verificable y solución. Intenta resolverlo antes de comparar.

Punto de partida​

Inventario.java
import java.util.HashMap;
import java.util.Map;

public class Inventario {
private final Map<String, Integer> unidades = new HashMap<>();

public void reponer(String referencia, int cantidad) {
unidades.put(referencia, unidades.getOrDefault(referencia, 0) + cantidad);
}

public int consultar(String referencia) {
return unidades.getOrDefault(referencia, 0);
}

public void retirar(String referencia, int cantidad) {
unidades.put(referencia, consultar(referencia) - cantidad);
}
}

No hay build ni pruebas y es posible obtener existencias negativas.

1. Crear una estructura reconocible​

Situación: los archivos sueltos obligan a explicar cómo compilar. Objetivo: adoptar la estructura estándar de Maven y un paquete propio. Criterio: mvn compile compila Inventario.java.

Solución​

inventario/
├── pom.xml
└── src/
├── main/java/es/skilly/inventario/Inventario.java
└── test/java/es/skilly/inventario/InventarioTest.java

Ambas clases declaran package es.skilly.inventario;. La ubicación refleja el paquete y separa producción de pruebas.

2. Definir un build para Java 17​

Situación: aún dependemos de comandos particulares. Objetivo: describir el proyecto y fijar Java. Criterio: mvn clean compile termina con BUILD SUCCESS.

Solución​

pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>es.skilly</groupId>
<artifactId>inventario</artifactId>
<version>1.0.0</version>

<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.14.2</junit.version>
<slf4j.version>2.0.17</slf4j.version>
</properties>
</project>

El POM ya expresa coordenadas, encoding y Java 17.

3. Corregir el build​

Situación: el POM declara Java 17, pero deja que Maven seleccione implícitamente la versión del compilador. Objetivo: fijar el plugin que aplica esa configuración. Criterio: mvn clean compile usa release 17 y crea clases bajo target/classes.

Solución​

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
</plugins>
</build>

Fijar el plugin evita que dos instalaciones elijan versiones implícitas diferentes. La propiedad maven.compiler.release continúa siendo la fuente de la versión objetivo.

4. Añadir JUnit con el scope correcto​

Situación: no hay una comprobación automática. Objetivo: incorporar JUnit Jupiter solo para pruebas. Criterio: mvn dependency:tree muestra JUnit con scope test.

Solución​

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>

JUnit no forma parte de la aplicación final; por eso su scope es test. Añade Surefire junto al plugin de compilación que ya existe dentro de <build><plugins>.

5. Capturar el comportamiento válido​

Situación: antes de corregir el defecto conviene proteger lo que funciona. Objetivo: probar reposición, acumulación y consulta inexistente. Criterio: tres pruebas pasan con mvn test.

Solución​

InventarioTest.java
package es.skilly.inventario;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class InventarioTest {
private Inventario inventario;

@BeforeEach
void preparar() {
inventario = new Inventario();
}

@Test
void reponeUnidades() {
inventario.reponer("TE-01", 4);
assertEquals(4, inventario.consultar("TE-01"));
}

@Test
void acumulaReposiciones() {
inventario.reponer("TE-01", 4);
inventario.reponer("TE-01", 3);
assertEquals(7, inventario.consultar("TE-01"));
}

@Test
void unaReferenciaDesconocidaTieneCeroUnidades() {
assertEquals(0, inventario.consultar("NO-EXISTE"));
}
}

Cada prueba prepara su propio inventario, actúa y verifica un resultado observable.

6. Reproducir y corregir el defecto​

Situación: retirar más unidades de las disponibles produce un valor negativo. Objetivo: crear una prueba roja y corregirlo. Criterio: la retirada imposible lanza IllegalArgumentException y conserva el estado.

Solución​

@Test
void rechazaUnaRetiradaSinExistencias() {
inventario.reponer("TE-01", 2);

assertThrows(IllegalArgumentException.class,
() -> inventario.retirar("TE-01", 3));
assertEquals(2, inventario.consultar("TE-01"));
}

Añade el import estático de assertThrows y corrige el método:

public void retirar(String referencia, int cantidad) {
int disponibles = consultar(referencia);
if (cantidad <= 0 || cantidad > disponibles) {
throw new IllegalArgumentException("Cantidad no disponible");
}
unidades.put(referencia, disponibles - cantidad);
}

La validación ocurre antes de modificar el mapa.

7. Añadir logging con intención​

Situación: una retirada rechazada no deja contexto. Objetivo: registrar operaciones relevantes sin sustituir las excepciones. Criterio: una retirada inválida registra referencia, cantidad y unidades disponibles.

Solución​

Añade slf4j-api sin scope y slf4j-simple con scope runtime, ambos en versión ${slf4j.version}. En Inventario:

private static final Logger logger = LoggerFactory.getLogger(Inventario.class);

public void retirar(String referencia, int cantidad) {
int disponibles = consultar(referencia);
if (cantidad <= 0 || cantidad > disponibles) {
logger.warn("Retirada rechazada: referencia={}, solicitadas={}, disponibles={}",
referencia, cantidad, disponibles);
throw new IllegalArgumentException("Cantidad no disponible");
}
unidades.put(referencia, disponibles - cantidad);
logger.info("Retirada completada: referencia={}, unidades={}", referencia, cantidad);
}

Importa Logger y LoggerFactory. Los placeholders separan patrón y valores.

8. Documentar el contrato​

Situación: no está claro qué ocurre ante una retirada no válida. Objetivo: documentar el comportamiento público. Criterio: mvn javadoc:javadoc genera el contrato sin errores.

Solución​

/**
* Retira unidades de una referencia existente.
*
* @param referencia referencia cuyo stock se modifica
* @param cantidad número positivo de unidades que se retiran
* @throws IllegalArgumentException si la cantidad no es positiva
* o supera las unidades disponibles
*/
public void retirar(String referencia, int cantidad) {
// implementación ya corregida
}

Configura maven-javadoc-plugin 3.11.2 dentro de <plugins>. El contrato explica restricciones y error, no el algoritmo.

9. Crear un README reproducible​

Situación: otra persona desconoce requisitos y comandos. Objetivo: ofrecer un recorrido desde un checkout limpio. Criterio: los comandos pueden copiarse sin instrucciones adicionales.

Solución​

README.md
# Inventario

Requisitos: Java 17 y Maven 3.9 o compatible.

## Verificar

```bash
mvn clean verify
```

## Generar la documentación

```bash
mvn javadoc:javadoc
```

Los resultados se generan bajo `target/` y no se versionan.

Reto final integrado​

Reconstruye el proyecto final en un directorio nuevo. Debe cumplir:

  • estructura Maven y paquete es.skilly.inventario;
  • compilación con Java 17;
  • JUnit Jupiter 5.14.2 en scope test;
  • SLF4J 2.0.17 con implementación en runtime;
  • cuatro pruebas, incluida la regresión de existencias negativas;
  • logging parametrizado en la retirada;
  • Javadoc del contrato y README con comandos reales;
  • ningún resultado de target/ versionado.

La solución es la suma de los nueve pasos. Compruébala desde la raíz:

mvn clean verify
mvn javadoc:javadoc

El primer comando debe ejecutar cuatro pruebas sin fallos y crear target/inventario-1.0.0.jar. El segundo debe crear target/reports/apidocs/index.html.

Revisión de calidad​

  • ¿Un fallo se reproduce mediante una prueba antes de corregirse?
  • ¿Las dependencias tienen versión y scope coherentes?
  • ¿Los logs aportan contexto sin duplicar la salida funcional?
  • ¿Javadoc describe el contrato real?
  • ¿El README solo contiene comandos ejecutados?
  • ¿mvn clean verify funciona sin archivos locales ocultos?

En la siguiente unidad aplicarás estas capacidades a un proyecto completo, preparado para entregarse y reproducirse desde cero.