Aller au contenu principal

Chapitre 1: Introduction aux Tests

Introduction​

Avant d'écrire votre premier test, il est crucial de comprendre pourquoi et comment tester efficacement. Ce chapitre pose les fondations d'une stratégie de test professionnelle.


🎯 Objectifs du Chapitre​

À la fin de ce chapitre, vous saurez:

  1. ✅ Pourquoi les tests sont essentiels
  2. ✅ Distinguer les différents types de tests
  3. ✅ Comprendre la pyramide des tests
  4. ✅ Configurer votre projet pour les tests
  5. ✅ Connaître les bonnes pratiques

1. Pourquoi Tester?​

Le Coût des Bugs​

Moment de DétectionCoût RelatifImpact
Pendant le développement1xAucun
En phase de test10xRetard
En production100xClient impacté, urgence

Les Bénéfices des Tests​

✅ Confiance: Le code fait ce qu'il doit faire
✅ Régression: Détecte les bugs introduits par des changements
✅ Documentation: Les tests expliquent le comportement attendu
✅ Design: Écrire des tests force un bon design (SOLID)
✅ Refactoring: Modifier le code sans peur
✅ Déploiement: CI/CD automatisé

Sans Tests vs Avec Tests​

// Sans tests 😰
public void deployToProduction() {
// "J'espère que ça va marcher..."
// "On verra bien en prod..."
// "Croisons les doigts..."
}

// Avec tests 😎
public void deployToProduction() {
// ✅ 347 tests passed
// ✅ Coverage: 85%
// ✅ All quality gates passed
// → Déploiement en confiance!
}

2. Types de Tests​

Vue d'Ensemble​

Tests Unitaires​

Quoi: Tester une unité de code en isolation (méthode, classe)

Caractéristiques:

  • Rapides (millisecondes)
  • Isolés (pas de BDD, pas de réseau)
  • Nombreux (majorité des tests)
@Test
void shouldCalculatePriceWithDiscount() {
// Teste UNIQUEMENT la logique de calcul
PriceCalculator calculator = new PriceCalculator();

double result = calculator.applyDiscount(100.0, 20);

assertEquals(80.0, result);
}

Tests d'Intégration​

Quoi: Tester plusieurs composants ensemble

Caractéristiques:

  • Plus lents (secondes)
  • Utilisent de vraies ressources (BDD, etc.)
  • Moins nombreux
@SpringBootTest
class OrderServiceIntegrationTest {

@Autowired
private OrderService orderService;

@Test
void shouldSaveOrderToDatabase() {
// Teste le service + repository + BDD
Order order = orderService.createOrder(/*...*/);

assertNotNull(order.getId());
}
}

Tests End-to-End (E2E)​

Quoi: Tester l'application complète comme un utilisateur

Caractéristiques:

  • Lents (minutes)
  • Environnement complet
  • Peu nombreux
// Avec Selenium par exemple
@Test
void userShouldBeAbleToCompleteCheckout() {
browser.navigate("/products");
browser.click("#add-to-cart");
browser.click("#checkout");
browser.fill("#address", "123 Main St");
browser.click("#confirm");

assertThat(browser.getText("#confirmation"))
.contains("Order confirmed");
}

3. La Pyramide des Tests​

Structure Idéale​

                    /\
/ \
/ E2E\ Peu, lents, fragiles
/ \
/────────\
/Integration\ Modéré
/ \
/────────────────\
/ Unit Tests \ Beaucoup, rapides, stables
/____________________\

Proportions Recommandées​

TypeProportionTemps d'Exécution
Unitaires70%~5 secondes
Intégration20%~30 secondes
E2E10%~5 minutes

Anti-Pattern: Le Cornet de Glace​

            ____________________
/ Beaucoup d'E2E \ ❌ Lents, fragiles
/────────────────────────\
/ Peu d'intégration \
/────────────────────────────\
/ Peu de tests \
/ unitaires \
/──────────────────────────────────\

❌ MAUVAIS: Tests lents, maintenance difficile

4. TDD et BDD​

TDD (Test-Driven Development)​

Écrire les tests AVANT le code:

// Étape 1: RED - Le test échoue (la méthode n'existe pas)
@Test
void shouldReturnEmptyListWhenNoProducts() {
ProductService service = new ProductService();
List<Product> products = service.findAll();
assertTrue(products.isEmpty());
}

// Étape 2: GREEN - Implémenter le minimum
public List<Product> findAll() {
return new ArrayList<>();
}

// Étape 3: REFACTOR - Améliorer si nécessaire

BDD (Behavior-Driven Development)​

Tests qui décrivent le comportement en langage naturel:

// Format Given-When-Then
@Test
void givenNewUser_whenRegister_thenAccountIsCreated() {
// Given (Contexte)
RegistrationRequest request = new RegistrationRequest(
"john", "john@email.com", "password123"
);

// When (Action)
User user = userService.register(request);

// Then (Résultat attendu)
assertNotNull(user.getId());
assertEquals("john", user.getUsername());
assertTrue(user.isEnabled());
}

5. Configuration Maven​

Dépendances de Test​

<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Test (inclut JUnit 5, Mockito, AssertJ) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- H2 pour les tests -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<!-- Security Test (optionnel) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Ce qui est inclus dans spring-boot-starter-test​

spring-boot-starter-test inclut:
├── JUnit 5 (Jupiter)
├── Mockito
├── AssertJ
├── Hamcrest
├── JSONPath
├── JsonAssert
└── Spring Test

Structure des Fichiers de Test​

src/
├── main/
│ └── java/
│ └── com/example/
│ ├── service/
│ │ └── ProductService.java
│ └── repository/
│ └── ProductRepository.java
└── test/
└── java/
└── com/example/
├── service/
│ └── ProductServiceTest.java ← Tests unitaires
├── repository/
│ └── ProductRepositoryTest.java ← Tests intégration
└── controller/
└── ProductControllerTest.java ← Tests API

6. Configuration de Test​

application-test.properties​

# src/test/resources/application-test.properties

# Base de données H2 en mémoire
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop

# Désactiver les logs verbeux
logging.level.org.hibernate.SQL=WARN
spring.jpa.show-sql=false

# Désactiver Flyway pour les tests (optionnel)
spring.flyway.enabled=false

Activer le Profil de Test​

@SpringBootTest
@ActiveProfiles("test") // ← Utilise application-test.properties
class MyIntegrationTest {
// ...
}

7. Exécuter les Tests​

Via Maven​

# Tous les tests
mvn test

# Tests avec rapport
mvn test jacoco:report

# Un test spécifique
mvn test -Dtest=ProductServiceTest

# Une méthode spécifique
mvn test -Dtest=ProductServiceTest#shouldFindProductById

# Skip les tests (à éviter!)
mvn install -DskipTests

Via l'IDE​

IntelliJ IDEA:
- Clic droit sur classe/méthode → Run Test
- Ctrl+Shift+F10 (Windows)
- Ctrl+Shift+R (Mac)

VS Code:
- Icône "Run Test" au-dessus de @Test
- Test Explorer dans la sidebar

8. Bonnes Pratiques​

Naming Convention​

// Format recommandé: should_ExpectedBehavior_When_StateUnderTest
@Test
void should_ReturnEmpty_When_NoProductsExist() { }

// Ou: given_When_Then
@Test
void given_ValidProduct_When_Save_Then_IdIsGenerated() { }

// Éviter:
@Test
void test1() { } // ❌ Non descriptif
@Test
void testProductService() { } // ❌ Trop vague

Pattern AAA (Arrange-Act-Assert)​

@Test
void shouldCalculateTotalPrice() {
// Arrange (Préparer)
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Item("Laptop", 1000.0));
cart.addItem(new Item("Mouse", 50.0));

// Act (Exécuter)
double total = cart.calculateTotal();

// Assert (Vérifier)
assertEquals(1050.0, total);
}

Un Test = Un Concept​

// ❌ MAUVAIS: Teste plusieurs choses
@Test
void testProduct() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
assertThrows(Exception.class, () -> p.applyDiscount(-5));
}

// ✅ BON: Un test par comportement
@Test
void shouldCreateProductWithNameAndPrice() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
}

@Test
void shouldApplyDiscountCorrectly() {
Product p = new Product("Test", 100.0);
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
}

@Test
void shouldRejectNegativeDiscount() {
Product p = new Product("Test", 100.0);
assertThrows(IllegalArgumentException.class,
() -> p.applyDiscount(-5));
}

9. Résumé​

Points Clés​

ConceptDescription
Test UnitaireTeste une unité en isolation
Test IntégrationTeste plusieurs composants ensemble
Test E2ETeste l'application complète
TDDTests écrits avant le code
AAAArrange-Act-Assert

Pyramide des Tests​

10% E2E (lents, fragiles)
20% Intégration (modérés)
70% Unitaires (rapides, nombreux)

Checklist​

✅ spring-boot-starter-test dans le pom.xml
✅ application-test.properties configuré
✅ Tests dans src/test/java
✅ Naming descriptif
✅ Un test = un concept
✅ Pattern AAA

🎯 Prochaine Étape​

Place à la pratique! Découvrons JUnit 5 en détail.

➡️ Chapitre 2: Tests Unitaires avec JUnit 5


⬅️ Retour au Module 9