Incluir información de CI/CD
This commit is contained in:
parent
db46202c78
commit
508ca0be33
@ -26,7 +26,7 @@
|
||||
<attribute name="optional" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-23">
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-23">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
@ -37,5 +37,6 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
||||
6
.settings/org.eclipse.core.resources.prefs
Normal file
6
.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/main/resources=UTF-8
|
||||
encoding//src/test/java=UTF-8
|
||||
encoding//src/test/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
@ -1,8 +1,8 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=23
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=17
|
||||
org.eclipse.jdt.core.compiler.compliance=23
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
@ -10,5 +10,5 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.processAnnotations=disabled
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=23
|
||||
|
||||
182
README.md
182
README.md
@ -1,115 +1,101 @@
|
||||
# Maven – Resumen rápido
|
||||
# CI/CD con Maven y GitHub Actions
|
||||
|
||||
## ¿Qué es Maven?
|
||||
## 1. Estructura del proyecto Maven
|
||||
|
||||
**Apache Maven** es una herramienta de gestión y automatización de proyectos Java.
|
||||
Un proyecto Maven bien organizado es la base para que el pipeline de CI funcione sin problemas:
|
||||
|
||||
Permite compilar, probar, empaquetar y gestionar dependencias de forma automática.
|
||||
Todo se configura mediante un archivo central llamado `pom.xml`.
|
||||
|
||||
---
|
||||
|
||||
## Conceptos básicos
|
||||
|
||||
### POM (Project Object Model)
|
||||
|
||||
Es el archivo `pom.xml`, donde se define:
|
||||
|
||||
* Información del proyecto (`groupId`, `artifactId`, `version`)
|
||||
* Dependencias
|
||||
* Plugins
|
||||
* Configuración de compilación
|
||||
|
||||
Ejemplo básico:
|
||||
```xml
|
||||
<groupId>org.ejemplo</groupId>
|
||||
<artifactId>mi-proyecto</artifactId>
|
||||
<version>1.0.0</version>
|
||||
```
|
||||
proyecto/
|
||||
├── src/
|
||||
│ ├── main/java/ # Código de producción
|
||||
│ └── test/java/ # Tests unitarios
|
||||
└── pom.xml # Dependencias, plugins y versión de Java
|
||||
```
|
||||
|
||||
> **Regla clave:** Si `mvn clean install` funciona en local → debe funcionar en CI.
|
||||
|
||||
---
|
||||
|
||||
## Ciclo de vida de Maven (Build Lifecycle)
|
||||
## 2. Dependencias y plugins esenciales
|
||||
|
||||
El ciclo de vida principal está formado por fases que se ejecutan en orden:
|
||||
### Testing
|
||||
- **`spring-boot-starter-test`** (proyectos Spring Boot): incluye JUnit 5, AssertJ y Mockito.
|
||||
|
||||
| Fase | Descripción |
|
||||
| -------- | ------------------------------------------- |
|
||||
| validate | Comprueba que el proyecto es correcto |
|
||||
| compile | Compila el código fuente |
|
||||
| test | Ejecuta los tests |
|
||||
| package | Empaqueta la aplicación (JAR/WAR) |
|
||||
| verify | Verifica que el paquete es válido |
|
||||
| install | Instala el paquete en el repositorio local |
|
||||
| deploy | Publica el paquete en un repositorio remoto |
|
||||
### Plugins Maven importantes
|
||||
|
||||
Ejemplo de ejecución:
|
||||
```bash
|
||||
mvn clean install
|
||||
| Plugin | Función |
|
||||
|--------|---------|
|
||||
| `maven-compiler-plugin` | Compila el proyecto |
|
||||
| `maven-surefire-plugin` | Ejecuta tests unitarios |
|
||||
| `maven-failsafe-plugin` | Ejecuta tests de integración (opcional) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Tests
|
||||
|
||||
Buenas prácticas para que los tests pasen en un entorno limpio de CI:
|
||||
|
||||
- Deben ejecutarse **sin depender del IDE**.
|
||||
- Evitar dependencias de rutas locales o bases de datos externas → usar **H2 en memoria** si es necesario.
|
||||
- Nombres descriptivos con un único escenario por test:
|
||||
```
|
||||
metodo_cuandoCondicion_retornaResultado
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Flujo básico con GitHub Actions
|
||||
|
||||
Archivo de configuración: `.github/workflows/maven.yml`
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '23'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn clean install
|
||||
```
|
||||
|
||||
---
|
||||
### ¿Qué hace `mvn clean install`?
|
||||
1. **clean** → Elimina artefactos anteriores
|
||||
2. **compile** → Compila el código fuente
|
||||
3. **test** → Ejecuta los tests unitarios
|
||||
4. **package** → Genera el `.jar` o `.war`
|
||||
|
||||
## Repositorios
|
||||
|
||||
Maven descarga las dependencias desde repositorios.
|
||||
|
||||
Tipos:
|
||||
|
||||
* **Local** → en el ordenador (`~/.m2`)
|
||||
* **Central** → repositorio público de Maven
|
||||
* **Remoto** → repositorios privados (Nexus, Artifactory)
|
||||
> Si algún test falla, GitHub Actions marcará el workflow como **Failed** ✗
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
## 5. Buenas prácticas
|
||||
|
||||
Las librerías necesarias para el proyecto se declaran en el `pom.xml`.
|
||||
|
||||
Ejemplo:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Maven descargará automáticamente la librería.
|
||||
|
||||
---
|
||||
|
||||
## Comandos Maven más usados
|
||||
|
||||
| Comando | Función |
|
||||
| ------------- | -------------------------------------------- |
|
||||
| `mvn compile` | Compila el proyecto |
|
||||
| `mvn test` | Ejecuta los tests |
|
||||
| `mvn package` | Genera el JAR/WAR |
|
||||
| `mvn install` | Instala el artefacto en el repositorio local |
|
||||
| `mvn clean` | Elimina la carpeta `target` |
|
||||
|
||||
---
|
||||
|
||||
## Estructura típica de un proyecto Maven
|
||||
```
|
||||
proyecto
|
||||
│
|
||||
├─ pom.xml
|
||||
└─ src
|
||||
├─ main
|
||||
│ ├─ java
|
||||
│ └─ resources
|
||||
└─ test
|
||||
└─ java
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Idea clave
|
||||
|
||||
Maven sigue el principio **"Convention over Configuration"**:
|
||||
si respetas su estructura estándar de proyecto, **necesitas muy poca configuración**.
|
||||
|
||||
---
|
||||
Fuentes: [ChatGPT](https://chat.openai.com) + [Claude](https://claude.ai)
|
||||
1. **Mantén el `pom.xml` limpio**: no sobreescribas versiones de dependencias gestionadas por Spring Boot.
|
||||
2. **Evita dependencias del entorno local**: sin rutas absolutas ni servicios externos sin mock.
|
||||
3. **Usa perfiles Maven** si necesitas configuraciones distintas para CI (p. ej., base de datos H2 para tests).
|
||||
4. **Verifica la versión de Java**: el runner de GitHub Actions debe usar la misma que la configurada en el proyecto.
|
||||
5. **Habilita la caché de Maven**: reduce significativamente el tiempo de build en cada ejecución.
|
||||
10
pom.xml
10
pom.xml
@ -16,6 +16,7 @@
|
||||
|
||||
<properties>
|
||||
<java.version>23</java.version>
|
||||
<junit.version>6.0.3</junit.version>
|
||||
</properties>
|
||||
|
||||
|
||||
@ -25,11 +26,20 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Starter test: incluye JUnit 5, Mockito, AssertJ, etc -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -6,9 +6,9 @@ import org.lapaloma.mapamundi.vo.Continente;
|
||||
|
||||
public interface IContinenteDAO {
|
||||
public Continente obtenerContinentePorClave(String codigo) ;
|
||||
public Continente actualizarContinente(Continente coche) ;
|
||||
public Continente crearContinente(Continente coche);
|
||||
public void borrarContinente(Continente coche);
|
||||
public void actualizarContinente(Continente continente) ;
|
||||
public void crearContinente(Continente continente);
|
||||
public void borrarContinente(Continente continente);
|
||||
public List<Continente> obtenerListaContinentes();
|
||||
public List<Continente> obtenerContinentePorNombre(String nombre);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Continente actualizarContinente(Continente continente) {
|
||||
public void actualizarContinente(Continente continente) {
|
||||
|
||||
String sentenciaSQL = """
|
||||
UPDATE T_CONTINENTE
|
||||
@ -64,11 +64,10 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return continente;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Continente crearContinente(Continente continente) {
|
||||
public void crearContinente(Continente continente) {
|
||||
|
||||
String sentenciaSQL = """
|
||||
INSERT INTO T_CONTINENTE (codigo, nombre_continente)
|
||||
@ -88,8 +87,6 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return continente;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
package org.lapaloma.mapamundi.excepcion;
|
||||
|
||||
public class ContinenteNoEncontradoException extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -3344627619585104664L;
|
||||
|
||||
public ContinenteNoEncontradoException(String mensaje) {
|
||||
super(mensaje);
|
||||
}
|
||||
}
|
||||
@ -7,36 +7,60 @@ import java.util.List;
|
||||
|
||||
import org.lapaloma.mapamundi.dao.IContinenteDAO;
|
||||
import org.lapaloma.mapamundi.dao.impl.ContinenteDaoJDBC;
|
||||
import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException;
|
||||
import org.lapaloma.mapamundi.vo.Continente;
|
||||
|
||||
/**
|
||||
* Isidoro Nevares Martín - Virgen de la Paloma Fecha creación: 13 mar 2026
|
||||
*/
|
||||
public class ContinenteService {
|
||||
|
||||
IContinenteDAO continenteDAO = new ContinenteDaoJDBC();
|
||||
|
||||
public Continente obtenerContinentePorClave(String codigo) {
|
||||
Continente continente = null;
|
||||
|
||||
continente = continenteDAO.obtenerContinentePorClave(codigo);
|
||||
|
||||
if (codigo == null || codigo.isBlank()) {
|
||||
throw new IllegalArgumentException("Código inválido");
|
||||
}
|
||||
|
||||
Continente continente = continenteDAO.obtenerContinentePorClave(codigo);
|
||||
|
||||
// Esta línea simula un error de negocio, ignorando lo que devuelve el DAO
|
||||
continente = null;
|
||||
|
||||
|
||||
if (continente == null) {
|
||||
throw new ContinenteNoEncontradoException(
|
||||
"No existe el continente con código: " + codigo
|
||||
);
|
||||
}
|
||||
|
||||
return continente;
|
||||
}
|
||||
|
||||
public List<Continente> obtenerListaContinentes() {
|
||||
List<Continente> listaContinentes = null;
|
||||
|
||||
listaContinentes = continenteDAO.obtenerListaContinentes();
|
||||
List<Continente> lista = continenteDAO.obtenerListaContinentes();
|
||||
|
||||
return listaContinentes;
|
||||
if (lista == null || lista.isEmpty()) {
|
||||
throw new RuntimeException("No hay continentes disponibles");
|
||||
}
|
||||
|
||||
return lista;
|
||||
}
|
||||
|
||||
public List<Continente> obtenerContinentePorNombre(String nombre) {
|
||||
List<Continente> listaContinentes = null;
|
||||
|
||||
listaContinentes = continenteDAO.obtenerContinentePorNombre(nombre);
|
||||
if (nombre == null || nombre.isBlank()) {
|
||||
throw new IllegalArgumentException("Nombre inválido");
|
||||
}
|
||||
|
||||
return listaContinentes;
|
||||
List<Continente> lista = continenteDAO.obtenerContinentePorNombre(nombre);
|
||||
|
||||
if (lista == null || lista.isEmpty()) {
|
||||
throw new ContinenteNoEncontradoException(
|
||||
"No existen continentes con nombre: " + nombre
|
||||
);
|
||||
}
|
||||
|
||||
return lista;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package org.lapaloma.mapamundi.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lapaloma.mapamundi.dao.IContinenteDAO;
|
||||
import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException;
|
||||
import org.lapaloma.mapamundi.vo.Continente;
|
||||
|
||||
class ContinenteServiceTest {
|
||||
|
||||
private ContinenteService continenteService;
|
||||
private FakeContinenteDAO fakeDAO;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
fakeDAO = new FakeContinenteDAO();
|
||||
continenteService = new ContinenteService();
|
||||
continenteService.continenteDAO = fakeDAO;
|
||||
}
|
||||
|
||||
// =========================
|
||||
// obtenerContinentePorClave
|
||||
// =========================
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorClave_cuandoCodigoEsNull_lanzaExcepcion() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
continenteService.obtenerContinentePorClave(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorClave_cuandoCodigoEstaVacio_lanzaExcepcion() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
continenteService.obtenerContinentePorClave("");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorClave_cuandoNoExiste_lanzaExcepcion() {
|
||||
assertThrows(ContinenteNoEncontradoException.class, () -> {
|
||||
continenteService.obtenerContinentePorClave("XX");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorClave_cuandoExiste_retornaContinente() {
|
||||
fakeDAO.crearContinente(new Continente("EU", "Europa"));
|
||||
|
||||
Continente resultado = continenteService.obtenerContinentePorClave("EU");
|
||||
|
||||
assertNotNull(resultado);
|
||||
assertEquals("Europa", resultado.getNombre());
|
||||
}
|
||||
|
||||
// =========================
|
||||
// obtenerListaContinentes
|
||||
// =========================
|
||||
|
||||
@Test
|
||||
void obtenerListaContinentes_cuandoListaEstaVacia_lanzaExcepcion() {
|
||||
assertThrows(RuntimeException.class, () -> {
|
||||
continenteService.obtenerListaContinentes();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerListaContinentes_cuandoHayDatos_retornaLista() {
|
||||
fakeDAO.crearContinente(new Continente("EU", "Europa"));
|
||||
|
||||
List<Continente> resultado = continenteService.obtenerListaContinentes();
|
||||
|
||||
assertNotNull(resultado);
|
||||
assertEquals(1, resultado.size());
|
||||
}
|
||||
|
||||
// =========================
|
||||
// obtenerContinentePorNombre
|
||||
// =========================
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorNombre_cuandoNombreEsNull_lanzaExcepcion() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
continenteService.obtenerContinentePorNombre(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorNombre_cuandoNombreEstaVacio_lanzaExcepcion() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
continenteService.obtenerContinentePorNombre("");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorNombre_cuandoNoExiste_lanzaExcepcion() {
|
||||
assertThrows(ContinenteNoEncontradoException.class, () -> {
|
||||
continenteService.obtenerContinentePorNombre("Inexistente");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void obtenerContinentePorNombre_cuandoExiste_retornaLista() {
|
||||
fakeDAO.crearContinente(new Continente("EU", "Europa"));
|
||||
|
||||
List<Continente> resultado = continenteService.obtenerContinentePorNombre("Europa");
|
||||
|
||||
assertEquals(1, resultado.size());
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Fake DAO. Se crea el DAO dentro del test para no depender de la conexión a la base de datos, de si hay red, de si accede a un fichero...
|
||||
// En caso de usar el DOA real (ContinenteDaoJDBC) estaríamos hablando de prubeas de integración.
|
||||
// =========================
|
||||
|
||||
static class FakeContinenteDAO implements IContinenteDAO {
|
||||
|
||||
private List<Continente> data = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Continente obtenerContinentePorClave(String codigo) {
|
||||
return data.stream()
|
||||
.filter(c -> c.getCodigo().equals(codigo))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Continente> obtenerListaContinentes() {
|
||||
return new ArrayList<>(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Continente> obtenerContinentePorNombre(String nombre) {
|
||||
List<Continente> resultado = new ArrayList<>();
|
||||
|
||||
for (Continente c : data) {
|
||||
if (c.getNombre().equals(nombre)) {
|
||||
resultado.add(c);
|
||||
}
|
||||
}
|
||||
return resultado;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actualizarContinente(Continente continente) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void crearContinente(Continente continente) {
|
||||
data.add(continente);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void borrarContinente(Continente continente) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user