Quelques librairies de tests
Généralement, automatiser des tests requiert trois choses:
- L'outil ou le moteur d'exécution des tests et de restitution des résultats (sous forme visuelle ou dans un fichier / rapport)
- Du code de test (soit le même langage que le projet, soit un langage différent) qui contient des assertions
- Une librairie ou framework de test (qui fait le lien entre le code et le moteur)
JUnit
Framework de test Java permet de tester son code via des assertions. Explorons le projet de démarrage fourni par le guide officiel
Exercices
- Ecrire un test unitaire
StringTest
qui test ces méthodes de la classeString
:toUpperCase()
,toLowerCase()
etcharAt()
. - Exo 2 de cette série
- Ignorer les CE invalides b5 et b6
- Exercice 2 de cette série qui nécessite d'abord de faire d'abord ces exercices: partie1, partie2
- Cet exo de librecours.net. Le code est à traduire du JS vers du Java.
- Cet exercice de librecours.net. Le code est à traduire du JS vers du Java.
- Ce TD qui provient de labri. Le
makefile
est l'équivalent demaven
pour nous. Faire la question 3 avec cette commande à la place de l'outil proposémvn surefire-report:report
.
Quelques astuces
- Exo2 : Fonctions corriger / modifier:
checkDay
,checkYear
,checkDayLimits
, renommer ou enlevermaxDayOfMonth(month)
,testFebruaryValidLimitDates
,testValidLimitDates
- Quelques solutions
Tests dans un projet Spring MVC
API REST sans base de données
- Générer un projet Spring avec initializr, en choisissant les dépendances suivantes: Spring Web et Spring Boot DevTools.
- Ouvrir le projet sur VSCode
-
Créer un
@RestController
avec deux routes en @GET et en @POST, un modèle et un service qui gère une liste statique en mémoire.- Je vous propose d'utiliser comme modèle une classe
Manga
avec trois champsISBN
,name
etnbPages
package com.cours.testlog.model; public class Manga { private String isbn; private String name; private int nbPages; public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNbPages() { return nbPages; } public void setNbPages(int nbPages) { this.nbPages = nbPages; } }
package com.cours.testlog.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.cours.testlog.model.Manga; import com.cours.testlog.service.MangaService; @RestController @RequestMapping("/manga") public class MangaController { @Autowired MangaService mangaService; @GetMapping public List<Manga> getAll() { return mangaService.getAll(); } @PostMapping public ResponseEntity<Void> addOne(@RequestBody Manga manga) { if (mangaService.addOne(manga)) { return ResponseEntity.ok().build(); } return ResponseEntity.badRequest().build(); } }
package com.cours.testlog.service; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import com.cours.testlog.model.Manga; @Service public class MangaService { static List<Manga> mangas = new ArrayList<>(); public List<Manga> getAll() { return mangas; } public boolean addOne(Manga manga) { if (manga.getIsbn() == null || mangas.stream().anyMatch(m -> m.getIsbn().equals(manga.getIsbn()))) { return false; } mangas.add(manga); return true; } public void removeAll() { mangas.clear(); } }
- Je vous propose d'utiliser comme modèle une classe
-
Lancer votre serveur et vérifier qu'il fonctionne avec la bonne commande (avec gradle
./gradlew bootRun
, avec maven./mvnw springboot:run
) ou depuis votre IDE - Écrire des tests unitaires pour le service
-
Spring propose deux façons de tester le contrôleur (en d'autres termes l'API REST).
- En lançant un serveur web (avec la stack HTTP complète) via la classe
TestRestTemplate
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class MangaControllerTests { @Autowired private TestRestTemplate restTemplate; @Autowired private MangaService mangaService; @BeforeEach void setup() { mangaService.removeAll(); } @Test public void testGetAll() { // https://www.baeldung.com/spring-rest-template-list var response = restTemplate.getForEntity("/manga", Manga[].class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(0, response.getBody().length); Manga manga = new Manga(); manga.setIsbn("sdsfds"); manga.setName("my hero academia"); manga.setNbPages(40); restTemplate.postForEntity("/manga", manga, Void.class); response = restTemplate.getForEntity("/manga", Manga[].class); assertNotNull(response.getBody()); assertEquals(1, response.getBody().length); Manga responseManga = (Manga) response.getBody()[0]; org.assertj.core.api.Assertions.assertThat(responseManga.getIsbn()).isEqualTo(manga.getIsbn()); assertEquals(manga.getIsbn(), responseManga.getIsbn()); } @Test public void testCannotAddSameIsbnTwice() { //TODO } }
- En lançant un serveur bouchonné (on n'a pas la stack HTTP complète) via la classe
MockMVC
.
package com.cours.testlog.controller; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.hamcrest.Matchers.containsString; import com.cours.testlog.service.MangaService; // https://spring.io/guides/gs/testing-web/ @SpringBootTest @AutoConfigureMockMvc public class MangaControllerMockTests { @Autowired private MockMvc mockMvc; @Autowired private MangaService mangaService; @BeforeEach void setup() { mangaService.removeAll(); } @Test public void testGetAll() throws Exception { this.mockMvc.perform(get("/manga")) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().string(containsString("[]"))); } @Test public void testAddOne() { } }
- En lançant un serveur web (avec la stack HTTP complète) via la classe
-
Écrire le test de
testCannotAddSameIsbnTwice
qui vérifie que l'on ne peut pas ajouter deux mangas avec le même ISBN. Implémenter ce test avecTestRestTemplate
etMockMVC
. - Ajouter une méthode PUT qui permet de modifier un manga existant (erreur 404 si le manga n'existe pas). Ajouter un test pour cette méthode.
- Ajouter une méthode DELETE qui permet de supprimer un manga existant (erreur 404 si le manga n'existe pas). Ajouter un test pour cette méthode.
API REST avec une base de données
Nous allons utiliser la BDD H2 pour sa simplicité car c'est une BDD relationnelle (SQL) qui ne nécessite pas de serveur et réside en mémoire (RAM) par défaut.
- Générer un projet Spring avec le créateur en ligne en choisissant les dépendances suivantes: Spring Data JPA, H2 Database, Spring Boot DevTool et Spring Web.
- Ouvrir le projet sur VSCode
- Créer une classe "Model" avec l'annotation
@Entity
et les annotations@Id
et@GeneratedValue
sur sa clé primaire. - Créer une interface
xxxxRepository: JpaRepository<Product, Long>
où xxx est le nom de votre modèle - Créer un contrôleur avec les routes en @GET, @POST, PUT et DELETE, un modèle et un service qui gère communique avec votre repository
- Relancer les tests et s'assurer qu'ils fonctionnent toujours.
Codes sources
Liens et références
- Anecdote: SQLite est réputé pour avoir une proportion importante de tests.
- http://deptinfo.cnam.fr/~graffion/UES/GLG101/tps/java/index.html
- https://gayerie.dev/docs/testing/test/junit.html
- https://github.com/mjeanroy/exercices-java/blob/master/exercices-junit.txt