Aller au contenu

Quelques librairies de tests

Généralement, automatiser des tests requiert trois choses:

  1. L'outil ou le moteur d'exécution des tests et de restitution des résultats (sous forme visuelle ou dans un fichier / rapport)
  2. Du code de test (soit le même langage que le projet, soit un langage différent) qui contient des assertions
  3. 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

  1. Ecrire un test unitaire StringTest qui test ces méthodes de la classe String: toUpperCase(), toLowerCase() et charAt().
  2. Exo 2 de cette série
    • Ignorer les CE invalides b5 et b6
  3. Exercice 2 de cette série qui nécessite d'abord de faire d'abord ces exercices: partie1, partie2
  4. Cet exo de librecours.net. Le code est à traduire du JS vers du Java.
  5. Cet exercice de librecours.net. Le code est à traduire du JS vers du Java.
  6. Ce TD qui provient de labri. Le makefile est l'équivalent de maven 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 enlever maxDayOfMonth(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 champs ISBN, name et nbPages
    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();
      }
    }
    
  • 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() {
    
      }
    }
    
  • É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 avec TestRestTemplate et MockMVC.

  • 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