Initial commit

This commit is contained in:
dahoud
2025-10-01 01:37:34 +00:00
commit f2bb633142
310 changed files with 86051 additions and 0 deletions

493
TESTING.md Normal file
View 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