🤯
Les tests manuels
Les tests de charge
Les tests end-to-end
Les tests de contrat
Les tests d’intégrations
Les tests unitaires
🤯
Les tests manuels
Les tests de charge
Les tests end-to-end
Les tests de contrat
Les tests d’intégrations
Les tests unitaires

🏃 Fast // Rapide
🏝 Independent // Indépendant
🔁 Repeatable // Répétable
✅ Self-validating // Auto-validé
📉 Thorough // Complet
on veut un feedback rapide
on veut rester concentré
test lent = pas exécuté ⇒ test inutile
une seule raison d’échouer
pas de dépendance extérieure
système de fichier, bdd, random, date, …
autre test
Toujours le même résultat
Sinon, pas de confiance dans le test ⇒ test inutile
la validation est automatique
les conditions de validation sont incluses dans le test
les cas d’usage nominaux (happy path)
les cas aux limites (edge case)
les cas d’erreurs ultimes
base de données HS
HTTP 500 sur une API REST
un seul niveau
pas de structures (if/boucles)
On a besoin au minimum :
d'une syntaxe pour écrire définir les tests
d’instructions pour vérifier les résultats : les assertions
d'un outil d’exécution
Idéalement, on pourra aussi s’aider :
d’un outil de mesure du code testé : le coverage
d’instructions pour faciliter l’isolation : les mocks
Jest, pour javascript, typescript
JUnit / AssertJ (assertions) / Jacoco (coverage) / Mockito (mocks) pour java
Certains langages récents intègrent nativement des outils de tests :
Rust
Elixir
ça dépend de votre outil
généralement séparé du code de production ⇒ garder des limites claires ⇒ ne pas envoyer du code de test en production par erreur
Exemples :
Jest : __tests__
Java : src/test/java
mvn test

Trois étapes :
Arrange
Act
Assert
@Test
void rollDice_return1() {
/* Arrange */
var randomGenerator = mock(RandomGenerator.class);
when(randomGenerator.nextInt()).thenReturn(1);
var dice = new Dice(randomGenerator);
/* Act */
int result = dice.roll();
/* Assert */
assertThat(result).isEqualTo(1);
}pour vérifier le résultat obtenu
idéalement une seule par test
En JS/TS avec Jest (🌐 Documentation)
expect(age).toEqual(34)
expect(age).not.toBeLessThan(64)
expect(password).toMatch("[A-Za-z0-9]+")En Java avec AssertJ (🌐 Documentation)
assertThat(age).isBetween(18, 100);
assertThat(wordList).containsExactlyInAnyOrder("foo", "border");
assertThat(password).matches("[a-zA-Z0-9+-$*!]+")mesure du code exercé par les tests
utile pour détecter les zones non testées
attention à la quête impossible des 100%
permettent de donner rapidement une implémentation différente
on peut faire des assertions sur leur utilisation
@Test
void rollDice_return1_withMock() {
/* Arrange */
var randomGenerator = mock(RandomGenerator.class);
when(randomGenerator.nextInt()).thenReturn(1);
var dice = new Dice(randomGenerator);
/* Act */
int result = dice.roll();
/* Assert */
assertThat(result).isEqualTo(1);
verify(randomGenerator, times(1)).nextInt();
}permettent également de donner une implémentation différente
avantage : réutilisable dans d’autres tests ou dans le code de production
inconvénient : plus difficile de faire des assertions dessus
@Test
void rollDice_return1_withTestDouble() {
/* Arrange */
RandomGenerator randomGenerator = () -> 1;
var dice = new Dice(randomGenerator);
/* Act */
int result = dice.roll();
/* Assert */
assertThat(result).isEqualTo(1);
}Vous devez mettre autant de soin dans le code de test que dans le code de production
— Mathieu Barberot
Le code doit rester facile à lire
Les code smells existent aussi dans les tests
plusieurs assertions = plusieurs raisons d’échouer
tests fragiles
signe qu’on teste trop de chose d’un seul coup
⇒ limiter le nombre d’assertions à 1 ou 2 ⇒ faire plusieurs tests pour dispatcher les assertions en trop
@Test
void player_can_fight() {
// Arrange
var kevin = new Joueur("Kevin", 5, 100, 75, 30);
var slime = new Monstre("Slime", 5, 30, 25);
// Act
combatService.attack(kevin, slime);
// Arrange
assertThat(slime.getHp()).isZero();
assertThat(slime.isDead()).isTrue();
assertThat(kevin.getXp()).isZero();
assertThat(kevin.getLevel()).isEqualTo(6);
} @Test
void monster_can_die_in_combat() {
// Arrange
var kevin = new Joueur("Kevin", 5, 100, 75, 30);
var slime = new Monstre("Slime", 5, 30, 10);
// Act
combatService.attack(kevin, slime);
// Arrange
assertThat(slime.getHp()).isZero();
assertThat(slime.isDead()).isTrue();
} @Test
void player_gain_xp_on_monster_death() {
// Arrange
var kevin = new Joueur("Kevin", 5, 100, 75, 30);
var slime = new Monstre("Slime", 5, 30, 10);
// Act
combatService.attack(kevin, slime);
// Arrange
assertThat(kevin.getXp()).isEqualTo(85);
} @Test
void player_can_level_up_on_monster_death() {
// Arrange
var kevin = new Joueur("Kevin", 5, 100, 75, 30);
var slime = new Monstre("Slime", 5, 30, 25);
// Act
combatService.attack(kevin, slime);
// Arrange
assertThat(kevin.getLevel()).isEqualTo(6);
}quand les tests sont quasiment des copier/coller
@Test
void formatsDateToDayMonthYear() {
// Arrange
// Act
String formatted = parse("2023-01-01").format(ofPattern("dd/MM/yyyy"));
// Assert
assertThat(formatted).isEqualTo("01/01/2023");
}
@Test
void formatsDateToMonthNameAndDay() {
// Arrange
// Act
String formatted = parse("2024-12-25").format(ofPattern("MMMM d"));
// Assert
assertThat(formatted).isEqualTo("December 25");
}
@Test
void formatsDateToIso() { /* ... */ } @ParameterizedTest(name = "formats ''{0}'' with pattern ''{1}'' in ''{2}''")
@CsvSource({
"2023-01-01, dd/MM/yyyy, 01/01/2023",
"2024-12-25, MMMM d, December 25",
"2025-05-12, yyyy.MM.dd, 2025.05.12"
})
void formatsDateWithGivenPattern(String input, String pattern, String expected) {
// Arrange
LocalDate date = parse(input);
// Act
String formatted = date.format(ofPattern(pattern, Locale.US));
// Assert
assertThat(formatted).isEqualTo(expected);
}
// Arrangeclarifier le setup du test
réutiliser le code dans plusieurs tests
@Test
void anItemCanBeAddedIntoACart() {
// Arrange
Cart cart = new Cart();
// Act
cart.addItem(new Item(1, "Keyboard", 100));
// Assert
assertThat(cart.getItems()).hasSize(1);
} private Cart cart;
@BeforeEach
void setUp() {
cart = new Cart();
}
@Test
void anItemCanBeAddedIntoACartUsingBeforeEach() {
// Arrange
// Act
cart.addItem(new Item(1, "Keyboard", 100));
// Assert
assertThat(cart.getItems()).hasSize(1);
}⚠ cela peut rompre l’indépendance de vos tests !
peut être considéré comme un code smell
Duplication possible entre plusieurs suites de tests
Séparation du code = moins de lisibilité
Des tests peuvent ne pas utiliser la totalité du beforeEach
Factorisation plus générique car réutilisable entre les suites
Nommage des fonctions pour la lisibilité
Plusieurs méthodes pour configurer le strict minimum à chaque test
class CartFactory {
public static Cart createEmptyCart() {
return new Cart();
}
}
@Test
void multipleItemsCanBeAddedIntoACartUsingFactory() {
// Arrange
Cart emptyCart = CartFactory.createEmptyCart();
// Act
emptyCart.addItem(new Item(1, "Keyboard", 100));
emptyCart.addItem(new Item(2, "Mouse", 50));
// Assert
assertThat(emptyCart.getItems()).hasSize(2);
}Les personas
pousser le concept de factories
incarner des types d’utilisateurs
requiert une coordination de l’équipe
class LillyFactory {
public static Cart createCart() {
Cart cart = new Cart();
cart.addItem(new Item(1, "Gamer Keyboard", 200));
cart.addItem(new Item(2, "Gamer Mouse", 100));
return cart;
}
}
@Test
void lillyHasAnExpensiveCart() {
// Arrange
// Act
Cart lillysCart = LillyFactory.createCart();
// Assert
assertThat(lillysCart.getTotal()).isGreaterThan(250);
}Tester que les différentes parties sont bien branchées
Exemple:
Je tourne le volant ⇒ les roues tournent
Comme les tests unitaires
Mais avec moins de contraintes
🏃 Fast
Autant que possible
🏝 Independant
On accepte des dépendances, mais maîtrisées
Utiliser une base de données éphèmere
Utiliser des faux services tiers…
🔁 Repeatable
Toujours !
✅ Self-validated
Toujours !
🌕 Thorough
Le happy path et des edge cases
Pour une SPA :
Playwright
Cypress
Jest
Pour une API :
Bruno / Postman (ou équivalent)
Playwright
Hurl / Jetbrains Http Client
RestAssured pour Java
Installer et initialiser Playwright [1]
npm init playwright@latestInstaller et initialiser Playwright
Écrire un test
test('Home page has the right title in page metadata', async ({ page }) => {
// Arrange
// Act
await page.goto('/');
// Assert
await expect(page).toHaveTitle("Keyboard Factory");
});Installer et initialiser Playwright
Écrire un test
Exécuter une suite de test
npx playwright test
⇒ Documenter l’API
@OpenApi(
path = "/rover",
methods = {HttpMethod.POST},
summary = "Create a rover",
requestBody = @OpenApiRequestBody(
content = {@OpenApiContent(from = RoverCreationDto.class)},
required = true
),
responses = {@OpenApiResponse(status = "201")}
)
public static void create(Context ctx) {
/* ... */
}⇒ Documenter les structures de données
public record CreateActionDto(
@OpenApiRequired
@OpenApiDescription("Le nom du rover")
String rover,
@OpenApiRequired
@OpenApiDescription("L'action à effectuer : forward, backward, turn_left, turn_right")
String action
) {}⇒ Initialiser un client REST

⇒ Importer via la documentation OpenAPI



⇒ Configurer l’environnement


⇒ Exécuter une requête

⇒ Ajouter des assertions

⇒ Ou carrément écrire des tests en JS
