🤯
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
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 son 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
simplifier la syntaxe
fusionner plusieurs assertions
extraire le code dans une fonction
test("splice can replace an element", () => {
// Arrange
const animals = ["Cat", "Dog", "Bird", "Lion", "Elephant", "Ant"]
// Act
const removedItems = animals.splice(3, 1, "Lizard")
// Assert
expect(removedItems).toEqual(["Lion"])
expect(animals).not.toEqual(expect.arrayContaining(["Lion"]))
expect(animals).toEqual(expect.arrayContaining(["Lizard"]))
})
test("splice can replace an element", () => {
// Arrange
const animals = ["Cat", "Dog", "Bird", "Lion", "Elephant", "Ant"]
// Act
const removedItems = animals.splice(3, 1, "Lizard")
// Assert
expectArray(animals, {
removedItems: ["Lion"],
addedItems: ["Lizard"],
})
})
function expectArray(
actual: string[], { removedItems, addedItems }: ExpectedArrayItemsChanges
) {
expect(actual).not.toEqual(expect.arrayContaining(removedItems))
expect(actual).toEqual(expect.arrayContaining(addedItems))
}
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));
// Assert
assertThat(formatted).isEqualTo(expected);
}
// Arrange
clarifier 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 API :
Postman (ou équivalent)
Playwright
Hurl / Jetbrains Http Client
RestAssured pour Java
Pour une SPA
Cypress
Playwright
Jest
Avec Postman :
Documenter avec Open API / Swagger
@Operation(summary = "Create a rover")
@APIResponse(description = "success", responseCode = "201")
@POST
@Produces(MediaType.APPLICATION_JSON)
fun createRover(roverCreationDto: RoverCreationDto): RestResponse<String> {
val rover = roverRepository.create(roverCreationDto.name)
return ResponseBuilder
.created<String>(URI.create("/api/rover/${rover.id}"))
.build()
}
Avec Postman :
Documenter avec Open API / Swagger
@Schema(description = "Data to create a rover")
data class RoverCreationDto(
@Schema(description = "Name of the rover", required = true)
val name: String
)
Avec Postman :
Documenter avec Open API / Swagger
Importer dans le client REST
Avec Postman :
Documenter avec Open API / Swagger
Importer dans le client REST, puis configurer
Avec Postman :
Documenter avec Open API / Swagger
Importer dans le client REST
Exécuter une suite de requête
Avec Postman :
Documenter avec Open API / Swagger
Importer dans le client REST
Exécuter une suite de requête
Écrire un test
Avec Playwright:
Installer et initialiser Playwright [1]
npm init playwright@latest
Avec Playwright:
Installer et initialiser Playwright
Ecrire 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");
});
Avec Playwright:
Installer et initialiser Playwright
Ecrire un test
Exécuter une suite de test
npx playwright test