Depuis start.spring.io
start.spring.io
Tout tourne sur les shared runners (Linux) de GitLab. On va uiliser Maven pour les démonstrations et TP.
Arborescence de départ de notre projet minimaliste !
. ├─ src │ ├─ main/java/fr/exemple/gitlab/tp1/Tp1Application.java │ └─ main/java/fr/exemple/gitlab/tp1/controller/CoucouController.java └─ src/test/java/fr/exemple/gitlab/tp1/controller/CoucouControllerTest.java
Code de base (commun Maven/Gradle)
main/java/fr/exemple/gitlab/tp1/Tp1Application.java :
main/java/fr/exemple/gitlab/tp1/Tp1Application.java
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
main/java/fr/exemple/gitlab/tp1/controller/CoucouController.java :
main/java/fr/exemple/gitlab/tp1/controller/CoucouController.java
package fr.exemple.gitlab.tp1.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class CoucouController { @GetMapping("/coucou") public String coucou() { return "Coucou de Spring Boot"; } }
src/test/java/fr/exemple/gitlab/tp1/controller/CoucouControllerTest.java :
src/test/java/fr/exemple/gitlab/tp1/controller/CoucouControllerTest.java
package fr.exemple.gitlab.tp1.controller; import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; public class CoucouControllerTest { @Test void retourneCoucou() { var controller = new CoucouController(); assertThat(controller.coucou()).isEqualTo("Coucou de Spring Boot"); } }
Avec Maven (pom.xml) :
<?xml version="1.0" encoding="UTF-8"?> <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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.7</version> <relativePath/> </parent> <groupId>fr.exemple.gitlab</groupId> <artifactId>tp1</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>tp1</name> <description>Demo project for Spring Boot et GitLab</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
.gitlab-ci.yml (Maven)
.gitlab-ci.yml
# Pour commencer, uniquement avec un stage "build" stages: - build # ici, je nomme mon job "maven-build" je pourrai l'appeler Toto, ça marcherait aussi maven-build: stage: build image: maven:3.9-eclipse-temurin-17 ## je choisi maven 3.9 et Eclipse Témurin avec Java 17 depuis une image de DockerHub (docker pull maven:3.9-eclipse-temurin-17) variables: MAVEN_OPTS: "-Dmaven.test.skip=false" # Cette variable permet d'exécuter les tests unitaires prévues (maven_opts est une vairiable d'environnement Maven) on a le skip à false script: - mvn -B -ntp clean package # Nettoie, compile, exécute les tests, et valide le projet proprement dans un environnement CI silencieux. - ls -la target/ artifacts: paths: [ target/ ] expire_in: 1 week ## -B : c'est le mode Batch non intéractif indispensable ici avec gitlab ci ## -ntp : sert à supprimer la bannière maven 'no transfer progress' ## -q : mode dit quiet pour moins verbeux (enlever pour cette fois) ## clean : supprime le dossier target/ avant de compiler ## verify : fait le job, compile, teste et vérifie la validité du projet (finalement j'ai remplacé par package pour avoir un JAR) ## artifacts : le fichier conservé pendant 7 jours donc dans target/ on aura le .jar, les tests les rapports si besoin.
Sur GitLab :
Résultat que vous devez obtenir :
Version en enlevant le -q pour que ce soit plus verbeux (maven) et remplacement de verify par package pour être certain de générer notre application au format JAR dans le répertoire target :
-q
verify
package
target
Logiquement, vous devriez trouver dans artefacts un dossier nommé artefacts.zip qui contient le Jar exécutable que vous pouvez tester sur votre machine !
artefacts
artefacts.zip
Pour terminer, voici comment lancer votre application sur votre machine et tester dans votre navigateur :
Lancez la commande java pour exécuter votre JAR : java -jar tp1-0.0.1-SNAPSHOT.jar
java -jar tp1-0.0.1-SNAPSHOT.jar
Sur votre navigateur à l’adresse http://localhost:8080/coucou
http://localhost:8080/coucou
On a bien notre message que le contrôleur nous retourne !
Checkstyle
Spotless
Nous allons faire un peu de clean code… donc pour ce tp, faites du code Java moche, je pense que vous savez le faire ;))
clean code
Vous pouvez reprendre le TP1 ou en prendre un autre.
Pour vous permettre de lancer une analyse statique, voire même, de corriger votre code java, voici 2 API bien pratiques que nous allons utiliser dans ce tp2.
Cela vous aidera à générer du code propre et cohérent dans vos projets futures !
Checkstyle lit votre code sans l’exécuter pour vérifier qu’il respecte un ensemble de règles de style et de bonnes pratiques comme celle-ci :
Exemple concret (imaginons que ce soit votre code):
public class test { public static void main(String[] args){ System.out.println("coucou"); } }
Et bien Checkstyle vous listera vos erreurs du genre :
[ERROR] Line 1: Class 'test' should be in PascalCase. [ERROR] Line 2: 'method def' child has incorrect indentation level 0, expected level should be 4. [ERROR] Line 3: 'System.out' should not be used.
Voyons ce que fait son ami Spotless :
C’est simple, il fait le ménage automatique… Il corrige automatiquement le style du code (indentation, imports, formatage,…).
si vous utilisez Maven, vous pouvez les exécuter avec les commandes ci-dessous que nous allons utiliser dans notre fichier gitlab-ci.yaml
gitlab-ci.yaml
mvn checkstyle:check # pour l'analyse du code mvn spotless:check # la vérification mvn spotless:apply # la correction auto
Bref, vous devez ajouter ces 2 plugins dans votre ficher Maven (pom.xml) :
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.diffplug.spotless</groupId> <artifactId>spotless-maven-plugin</artifactId> <version>2.43.0</version> <configuration><java><googleJavaFormat/></java></configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <version>3.3.1</version> <configuration><configLocation>google_checks.xml</configLocation></configuration> </plugin> </plugins> </build>
.gitlab-ci.yml :
stages: - lint - test - build test-code: stage: lint image: maven:3.9-eclipse-temurin-17 # répété 3 fois pour la démo, on va simplifier après avec "default" script: - mvn -B -ntp checkstyle:check spotless:check test-unitaire: stage: test image: maven:3.9-eclipse-temurin-17 script: - mvn -B -ntp test maven-build: stage: build image: maven:3.9-eclipse-temurin-17 script: - echo "Compilation et exécution build..." - mvn -B -ntp clean package -DskipTests artifacts: paths: - target/ expire_in: 1 week
Pour le fun, voici la même classe que tout à l’heure mais en version pas clean !
Logiquement, votre code devrait s’arrêter au stage lint !
lint
Que faire ?
Cliquez sur le job pour voir les logs et découvrir les soucis de code, Spotless n’est pas content ;((
Vous voyez dans les logs, qu’il vous conseille de faire un Run 'mvn spotless:apply' to fix these violations. Les fichiers seront reformatés au format Google Java Format.
Run 'mvn spotless:apply' to fix these violations
Donc ici, on voit bien que Spotless a fait échoué le Job et vous propose corriger. Il a détecté 4 fichiers à reformater. Mais comment corriger ?
mvn spotless:apply mvn -q test # ici, optionnel git add -A git commit -m "style: format code with Spotless" git push
Ensuite, le pipeline drvait passer au vert (le CI vérifie avec spotless:check et les devs corrigent avec spotless:apply)
Voici une proposition de fichier gitlab-ci.yamlmodifié
stages: - lint - test - build # plus simple je mets une seule fois l'image docker default: image: maven:3.9-eclipse-temurin-17 # option 1 : style "puriste", on vérifie seulement (échoue si moche !) lint: stage: lint script: - mvn -B -ntp checkstyle:check spotless:check # option 2 : style "auto-fix" en CI (passe même si le repo n’est pas re-commité !) lint-autofix: stage: lint script: - mvn -B -ntp spotless:apply # corrige localement dans le job - mvn -B -ntp checkstyle:check spotless:check test-unitaire: stage: test script: - mvn -B -ntp test maven-build: stage: build script: - echo "compilation et exécution build..." - mvn -B -ntp clean package -DskipTests # ici, j'ai ajouté un skipTests puisqu'on les a déjà fait dans le job précédent artifacts: paths: - target/ expire_in: 1 week
On peut éventuellement ajouter la création de rapports des tests unitaires de JUnit dans notre Job “test-unitaire” :
test-unitaire: stage: test script: - mvn -B -ntp test artifacts: when: always reports: junit: target/surefire-reports/*.xml paths: - target/surefire-reports/ expire_in: 1 week
Rappel sur la commande mvn -B -ntp test
mvn -B -ntp test
mvn
-B
-ntp
test
Les étapes lorsque vous éxécutez mvn test :
mvn test
maven-surefire-plugin
target/surefire-reports/
Remarque : Vous avez remarqué que lors de la création de votre projet avec Spring Initializr, Spring Boot génére une classe de test d’intégration par défaut pour votre projet. Il est exécuté avec @SpringBootTest qui charge le contexte Spring complet. Même vide, gardez-le ! Il permet le test de démarrage global (intégration). Tandis que notre classe PasBeauControllerTest est un test unitaire classique qui n’utilise pas Spring Boot, on vérifie la logique interne.
@SpringBootTest
PasBeauControllerTest
Si vous enlevez la classe Tp2ApplicationTests.java, tout fonctionnera mais si un @Bean est mal configuré, si application.properties contient une erreur de port ou de datasource, si une dépendance manquante empêche le démarrage de Spring, vous ne le verrez qu’au moment du run réel, pas au test !
Tp2ApplicationTests.java
@Bean
application.properties
Retenez que votre mvn test exécutera les 2 !
Exemple :
package fr.exemple.gitlab.tp2; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class Tp2ApplicationTests { @Test void contextLoads() { } }
Nos 2 tests n’ont pas le même rôle !
Tp2ApplicationTests
┌─────────────────────────────┐ │ .gitlab-ci.yml │ └─────────────────────────────┘ │ ▼ ┌────────────────────────────────────────┐ │ Pipeline CI/CD │ └────────────────────────────────────────┘ │ ┌───────────────────────┼────────────────────────┐ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ LINT │ │ TEST │ │ BUILD │ │ (Checkstyle│ │ (JUnit 5) │ │ (Packaging)│ │ + Spotless)│ │ │ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ │ │ ▼ ▼ ▼ ┌────────────┐ ┌────────────────────┐ ┌────────────────┐ │ Vérifie le │ │ Exécute les tests │ │ Produit le JAR │ │ style du │ │ unitaires et │ │ exécutable │ │ code Java │ │ d’intégration │ │ (target/*.jar) │ │ (formatage)│ │ avec Maven Surefire│ │ │ └────────────┘ └────────────────────┘ └────────────────┘ │ │ │ ▼ ▼ ▼ ✅ ou ❌ ✅ ou ❌ ✅ ou ❌ (style ok ?) (tests réussis ?) (build sans erreur ?)