Initial commit
This commit is contained in:
493
TESTING.md
Normal file
493
TESTING.md
Normal file
@@ -0,0 +1,493 @@
|
||||
# 🧪 TESTS - BTPXPRESS BACKEND
|
||||
|
||||
## 📋 Table des matières
|
||||
|
||||
- [Vue d'ensemble](#vue-densemble)
|
||||
- [Types de tests](#types-de-tests)
|
||||
- [Configuration](#configuration)
|
||||
- [Tests unitaires](#tests-unitaires)
|
||||
- [Tests d'intégration](#tests-dintégration)
|
||||
- [Couverture de code](#couverture-de-code)
|
||||
- [Bonnes pratiques](#bonnes-pratiques)
|
||||
- [CI/CD](#cicd)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Vue d'ensemble
|
||||
|
||||
### **Framework de tests**
|
||||
|
||||
- **JUnit 5** : Framework de tests
|
||||
- **Mockito** : Mocking
|
||||
- **RestAssured** : Tests API REST
|
||||
- **Testcontainers** : Tests avec Docker
|
||||
- **H2** : Base de données en mémoire pour tests
|
||||
|
||||
### **Objectifs de couverture**
|
||||
|
||||
| Type | Objectif | Actuel |
|
||||
|------|----------|--------|
|
||||
| **Ligne** | 80% | - |
|
||||
| **Branche** | 70% | - |
|
||||
| **Méthode** | 85% | - |
|
||||
|
||||
---
|
||||
|
||||
## 📚 Types de tests
|
||||
|
||||
### **1. Tests unitaires**
|
||||
|
||||
Testent une unité de code isolée (méthode, classe).
|
||||
|
||||
**Localisation** : `src/test/java/.../service/`
|
||||
|
||||
**Exemple** : `ChantierServiceTest.java`
|
||||
|
||||
### **2. Tests d'intégration**
|
||||
|
||||
Testent l'intégration entre plusieurs composants.
|
||||
|
||||
**Localisation** : `src/test/java/.../adapter/http/`
|
||||
|
||||
**Exemple** : `ChantierResourceTest.java`
|
||||
|
||||
### **3. Tests end-to-end**
|
||||
|
||||
Testent l'application complète avec base de données réelle.
|
||||
|
||||
**Localisation** : `src/test/java/.../e2e/`
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### **application-test.properties**
|
||||
|
||||
```properties
|
||||
# Base de données H2 en mémoire
|
||||
quarkus.datasource.db-kind=h2
|
||||
quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb
|
||||
quarkus.hibernate-orm.database.generation=drop-and-create
|
||||
quarkus.hibernate-orm.sql-load-script=import-test.sql
|
||||
|
||||
# Désactiver Keycloak pour les tests
|
||||
quarkus.oidc.enabled=false
|
||||
|
||||
# Logs
|
||||
quarkus.log.level=INFO
|
||||
quarkus.log.category."dev.lions.btpxpress".level=DEBUG
|
||||
```
|
||||
|
||||
### **Dépendances Maven**
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<!-- JUnit 5 -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- RestAssured -->
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Mockito -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-mockito</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testcontainers -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔬 Tests unitaires
|
||||
|
||||
### **Exemple : ChantierServiceTest**
|
||||
|
||||
```java
|
||||
@QuarkusTest
|
||||
class ChantierServiceTest {
|
||||
|
||||
@Inject
|
||||
ChantierService chantierService;
|
||||
|
||||
@InjectMock
|
||||
ClientService clientService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Devrait créer un chantier avec succès")
|
||||
void shouldCreateChantier() {
|
||||
// Given
|
||||
ChantierDTO dto = ChantierDTO.builder()
|
||||
.nom("Villa Moderne")
|
||||
.code("CHANT-001")
|
||||
.clientId(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
Client client = new Client();
|
||||
client.setId(dto.getClientId());
|
||||
when(clientService.findById(dto.getClientId()))
|
||||
.thenReturn(Optional.of(client));
|
||||
|
||||
// When
|
||||
Chantier result = chantierService.create(dto);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
assertEquals("Villa Moderne", result.getNom());
|
||||
assertEquals("CHANT-001", result.getCode());
|
||||
verify(clientService, times(1)).findById(dto.getClientId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Devrait lever une exception si le client n'existe pas")
|
||||
void shouldThrowExceptionWhenClientNotFound() {
|
||||
// Given
|
||||
ChantierDTO dto = ChantierDTO.builder()
|
||||
.nom("Villa Moderne")
|
||||
.clientId(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
when(clientService.findById(dto.getClientId()))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
// When & Then
|
||||
assertThrows(NotFoundException.class, () -> {
|
||||
chantierService.create(dto);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Devrait calculer le montant total correctement")
|
||||
void shouldCalculateTotalAmount() {
|
||||
// Given
|
||||
Chantier chantier = new Chantier();
|
||||
chantier.setMontantPrevu(new BigDecimal("100000.00"));
|
||||
|
||||
// When
|
||||
BigDecimal total = chantierService.calculateTotal(chantier);
|
||||
|
||||
// Then
|
||||
assertEquals(new BigDecimal("100000.00"), total);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Commandes**
|
||||
|
||||
```bash
|
||||
# Exécuter tous les tests unitaires
|
||||
./mvnw test
|
||||
|
||||
# Exécuter un test spécifique
|
||||
./mvnw test -Dtest=ChantierServiceTest
|
||||
|
||||
# Exécuter une méthode spécifique
|
||||
./mvnw test -Dtest=ChantierServiceTest#shouldCreateChantier
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Tests d'intégration
|
||||
|
||||
### **Exemple : ChantierResourceTest**
|
||||
|
||||
```java
|
||||
@QuarkusTest
|
||||
@TestHTTPEndpoint(ChantierResource.class)
|
||||
class ChantierResourceTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("GET /chantiers devrait retourner la liste des chantiers")
|
||||
void shouldGetAllChantiers() {
|
||||
given()
|
||||
.when()
|
||||
.get()
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("size()", greaterThan(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POST /chantiers devrait créer un chantier")
|
||||
@Transactional
|
||||
void shouldCreateChantier() {
|
||||
// Given
|
||||
ChantierDTO dto = ChantierDTO.builder()
|
||||
.nom("Villa Test")
|
||||
.code("TEST-001")
|
||||
.clientId(createTestClient())
|
||||
.statut(StatutChantier.PLANIFIE)
|
||||
.build();
|
||||
|
||||
// When & Then
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.body(dto)
|
||||
.when()
|
||||
.post()
|
||||
.then()
|
||||
.statusCode(201)
|
||||
.body("nom", equalTo("Villa Test"))
|
||||
.body("code", equalTo("TEST-001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GET /chantiers/{id} devrait retourner 404 si non trouvé")
|
||||
void shouldReturn404WhenChantierNotFound() {
|
||||
UUID randomId = UUID.randomUUID();
|
||||
|
||||
given()
|
||||
.pathParam("id", randomId)
|
||||
.when()
|
||||
.get("/{id}")
|
||||
.then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("PUT /chantiers/{id} devrait modifier un chantier")
|
||||
@Transactional
|
||||
void shouldUpdateChantier() {
|
||||
// Given
|
||||
UUID chantierId = createTestChantier();
|
||||
ChantierDTO updateDto = ChantierDTO.builder()
|
||||
.nom("Villa Modifiée")
|
||||
.build();
|
||||
|
||||
// When & Then
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
.pathParam("id", chantierId)
|
||||
.body(updateDto)
|
||||
.when()
|
||||
.put("/{id}")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("nom", equalTo("Villa Modifiée"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DELETE /chantiers/{id} devrait supprimer un chantier")
|
||||
@Transactional
|
||||
void shouldDeleteChantier() {
|
||||
// Given
|
||||
UUID chantierId = createTestChantier();
|
||||
|
||||
// When & Then
|
||||
given()
|
||||
.pathParam("id", chantierId)
|
||||
.when()
|
||||
.delete("/{id}")
|
||||
.then()
|
||||
.statusCode(204);
|
||||
|
||||
// Vérifier que le chantier est supprimé
|
||||
given()
|
||||
.pathParam("id", chantierId)
|
||||
.when()
|
||||
.get("/{id}")
|
||||
.then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
private UUID createTestClient() {
|
||||
Client client = new Client();
|
||||
client.setNom("Test Client");
|
||||
client.setEmail("test@example.com");
|
||||
client.persist();
|
||||
return client.getId();
|
||||
}
|
||||
|
||||
private UUID createTestChantier() {
|
||||
Chantier chantier = new Chantier();
|
||||
chantier.setNom("Test Chantier");
|
||||
chantier.setCode("TEST-" + System.currentTimeMillis());
|
||||
chantier.setStatut(StatutChantier.PLANIFIE);
|
||||
chantier.persist();
|
||||
return chantier.getId();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Commandes**
|
||||
|
||||
```bash
|
||||
# Exécuter tous les tests d'intégration
|
||||
./mvnw verify
|
||||
|
||||
# Exécuter un test spécifique
|
||||
./mvnw verify -Dit.test=ChantierResourceTest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Couverture de code
|
||||
|
||||
### **JaCoCo**
|
||||
|
||||
Configuration dans `pom.xml` :
|
||||
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.10</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
### **Générer le rapport**
|
||||
|
||||
```bash
|
||||
# Exécuter les tests avec couverture
|
||||
./mvnw clean test jacoco:report
|
||||
|
||||
# Ouvrir le rapport
|
||||
open target/site/jacoco/index.html
|
||||
```
|
||||
|
||||
### **Rapport de couverture**
|
||||
|
||||
Le rapport affiche :
|
||||
- Couverture par package
|
||||
- Couverture par classe
|
||||
- Lignes couvertes/non couvertes
|
||||
- Branches couvertes/non couvertes
|
||||
|
||||
---
|
||||
|
||||
## ✅ Bonnes pratiques
|
||||
|
||||
### **1. Nommage des tests**
|
||||
|
||||
```java
|
||||
// ❌ Mauvais
|
||||
@Test
|
||||
void test1() { }
|
||||
|
||||
// ✅ Bon
|
||||
@Test
|
||||
@DisplayName("Devrait créer un chantier avec succès")
|
||||
void shouldCreateChantierSuccessfully() { }
|
||||
```
|
||||
|
||||
### **2. Pattern AAA (Arrange-Act-Assert)**
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldCalculateTotal() {
|
||||
// Arrange (Given)
|
||||
Chantier chantier = new Chantier();
|
||||
chantier.setMontantPrevu(new BigDecimal("100000"));
|
||||
|
||||
// Act (When)
|
||||
BigDecimal total = service.calculateTotal(chantier);
|
||||
|
||||
// Assert (Then)
|
||||
assertEquals(new BigDecimal("100000"), total);
|
||||
}
|
||||
```
|
||||
|
||||
### **3. Tests indépendants**
|
||||
|
||||
Chaque test doit être indépendant et pouvoir s'exécuter seul.
|
||||
|
||||
```java
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Initialiser les données de test
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
// Nettoyer les données de test
|
||||
}
|
||||
```
|
||||
|
||||
### **4. Utiliser des données de test réalistes**
|
||||
|
||||
```java
|
||||
// ❌ Mauvais
|
||||
Chantier chantier = new Chantier();
|
||||
chantier.setNom("test");
|
||||
|
||||
// ✅ Bon
|
||||
Chantier chantier = Chantier.builder()
|
||||
.nom("Construction Villa Moderne")
|
||||
.code("CHANT-2025-001")
|
||||
.adresse("123 Rue de la Paix, 75001 Paris")
|
||||
.montantPrevu(new BigDecimal("250000.00"))
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 CI/CD
|
||||
|
||||
### **GitHub Actions**
|
||||
|
||||
`.github/workflows/tests.yml` :
|
||||
|
||||
```yaml
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Run tests
|
||||
run: ./mvnw clean verify
|
||||
|
||||
- name: Generate coverage report
|
||||
run: ./mvnw jacoco:report
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Dernière mise à jour**: 2025-09-30
|
||||
**Version**: 1.0
|
||||
**Auteur**: Équipe BTPXpress
|
||||
|
||||
Reference in New Issue
Block a user