Tests : sécurité, contrat, résilience, charge et les autres
L'idée en une phrase
Tests unitaires, d'intégration et de bout en bout couvrent l'essentiel. Mais certains problèmes leur échappent : une adresse accessible à n'importe qui, deux applications qui ne parlent plus le même format, une API externe qui ne répond plus, une règle d'architecture contournée. D'autres familles de tests existent pour ces cas précis. Cette fiche en fait le tour rapidement, pour que tu saches qu'elles existent et à quoi elles servent quand tu les croiseras.
Une image pour comprendre : les contrôles spécialisés
Dans l'usine des fiches précédentes, la voiture a passé le banc d'essai, le banc de montage et l'essai sur route. Avant de la vendre, elle passe encore des contrôles spécialisés : le crash-test, le test de résistance au froid, l'homologation, le contrôle antipollution. Chacun cherche un type de défaut précis, que les essais généraux ne cherchent pas.
Les tests de cette fiche sont ces contrôles spécialisés. On ne les met pas tous en place sur chaque projet : on choisit ceux qui répondent à un risque réel.
| Test | La question à laquelle il répond |
|---|---|
| sécurité | « Qui a le droit d'appeler cette adresse ? » |
| acceptation (Cucumber) | « L'application fait-elle ce que le métier a demandé ? » |
| contrat | « Les deux applications parlent-elles toujours le même format ? » |
| résilience | « Que se passe-t-il quand un service extérieur tombe ? » |
| batch | « Le traitement par lot gère-t-il tous les cas du fichier ? » |
| accessibilité | « Une personne malvoyante peut-elle utiliser la page ? » |
| architecture (ArchUnit) | « Les règles de découpage du code sont-elles respectées ? » |
| mutation (PIT) | « Mes tests détectent-ils vraiment les erreurs ? » |
| propriétés (jqwik) | « La règle tient-elle pour toutes les valeurs, pas seulement mes exemples ? » |
| charge | « L'application tient-elle avec 500 utilisateurs en même temps ? » |
| non-régression visuelle | « L'affichage a-t-il changé sans qu'on le veuille ? » |
Les tests de sécurité
À quoi ça sert. Vérifier que chaque adresse de l'API n'est accessible qu'aux bonnes personnes. C'est un oubli très courant : on ajoute une adresse d'administration, et on oublie de la protéger.
Exemple. Avec spring-security-test, dans un test @WebMvcTest (voir la fiche sur les tests d'intégration) :
@Test
void refuse_un_utilisateur_non_connecte() throws Exception {
mockMvc.perform(get("/admin/statistiques"))
.andExpect(status().isUnauthorized()); // 401 : pas connecté
}
@Test
@WithMockUser(roles = "CLIENT")
void refuse_un_client() throws Exception {
mockMvc.perform(get("/admin/statistiques"))
.andExpect(status().isForbidden()); // 403 : connecté, mais pas le bon rôle
}
@Test
@WithMockUser(roles = "ADMIN")
void accepte_un_administrateur() throws Exception {
mockMvc.perform(get("/admin/statistiques"))
.andExpect(status().isOk());
}
@WithMockUser simule un utilisateur connecté avec un rôle donné, sans passer par un vrai formulaire de connexion.
À savoir aussi. La sécurité, c'est aussi les bibliothèques du projet : une dépendance avec une faille connue est une faille dans ton application. Des outils (OWASP Dependency-Check, l'analyse des dépendances de GitLab) les signalent automatiquement dans le pipeline.
Quand. Sur toute API qui a des utilisateurs et des rôles : teste au moins le cas « non connecté » et le cas « pas le bon rôle » pour chaque adresse sensible.
Les tests d'acceptation avec Cucumber (BDD)
À quoi ça sert. Écrire les règles en phrases, dans un format que le métier (le product owner, l'expert fonctionnel) peut lire, relire et valider. Le même texte sert ensuite de test automatisé. On parle de BDD (Behaviour-Driven Development).
Exemple. Un fichier .feature, écrit en langage Gherkin :
# language: fr
Fonctionnalité: Plafond de commande
Scénario: Une commande au-delà du plafond est refusée
Étant donné un client actif avec un plafond de 1000 €
Quand il passe une commande de 1250 €
Alors la commande est refusée avec le motif "PLAFOND_DEPASSE"
Chaque phrase est reliée à une méthode Java :
public class PlafondEtapes {
@Given("un client actif avec un plafond de {int} €")
public void unClientActif(int plafond) { ... }
@When("il passe une commande de {int} €")
public void ilPasseUneCommande(int montant) { ... }
@Then("la commande est refusée avec le motif {string}")
public void laCommandeEstRefusee(String code) { ... }
}
Quand. Très présent dans les banques, les assurances et les grandes ESN, quand le métier participe à la définition des tests. Sur un petit projet sans interlocuteur métier, c'est souvent une couche de plus pour peu de bénéfice : de bons noms de tests unitaires suffisent.
Les tests de contrat
À quoi ça sert. Quand deux applications communiquent (un front-end et une API, ou deux services), vérifier qu'elles restent d'accord sur le format des échanges, sans devoir les démarrer ensemble.
Le principe.
- l'application cliente décrit ce qu'elle attend : « quand j'appelle
GET /produits/42, je reçois unnom(texte) et unprix(nombre) » ; c'est le contrat ; - l'application fournisseuse vérifie automatiquement, dans ses propres tests, qu'elle respecte ce contrat.
Si quelqu'un renomme prix en montant côté fournisseur, son pipeline échoue, avant que l'application cliente ne casse en production. C'est la protection automatique du tableau « modifications qui cassent les clients » de la fiche sur OpenAPI.
Les outils. Pact (le plus répandu, plusieurs langages) et Spring Cloud Contract (dans l'écosystème Spring).
Quand. Quand plusieurs équipes font évoluer des applications qui dépendent les unes des autres. Pour une seule application avec son front-end dans le même dépôt, les tests d'intégration suffisent généralement.
Les tests de résilience
À quoi ça sert. Vérifier ce que fait l'application quand un service extérieur se comporte mal : il répond en 30 secondes, renvoie des erreurs 500, ou ne répond plus du tout.
C'est un scénario fréquent en production, et rarement testé. Une API externe lente, sans délai d'attente maximal (timeout) côté client, bloque les threads et les connexions de ton application les uns après les autres (voir les fiches sur les threads et les métriques). Ton application tombe à cause d'un service qui n'est même pas le tien.
Exemple. Avec WireMock, qui simule un serveur HTTP, on prépare une réponse volontairement lente :
stubFor(get("/tarifs").willReturn(ok().withFixedDelay(30_000))); // répond en 30 secondes
assertThatThrownBy(() -> tarifClient.recupererTarifs())
.isInstanceOf(TarifsIndisponiblesException.class); // ✅ abandonne vite, avec une erreur claire
Le test vérifie que l'application abandonne au bout de quelques secondes, avec une erreur claire, au lieu d'attendre indéfiniment.
Quand. Dès que l'application dépend d'une API externe pour une fonction importante. Le minimum : un test qui vérifie le délai d'attente maximal, et un test qui vérifie la réaction à une erreur 500.
Les tests de batch
À quoi ça sert. Vérifier un traitement par lot : un import de fichier, un export, un calcul de nuit (voir les fiches sur les cron et sur Java sur mainframe). Ces traitements manipulent beaucoup de données, et les cas particuliers y sont nombreux.
Le principe. On prépare un petit fichier d'entrée représentatif, on lance le traitement, et on vérifie la base ou le fichier de sortie.
Les cas à couvrir en priorité :
- un fichier normal ;
- un fichier vide ;
- une ligne invalide au milieu : le traitement s'arrête-t-il, ou ignore-t-il la ligne en la signalant ?
- une reprise après un arrêt en cours de route : le traitement reprend-il sans créer de doublons ?
Quand. Pour tout traitement par lot qui touche à des données importantes. Si le projet utilise Spring Batch, il fournit ses propres outils de test (spring-batch-test).
Les tests d'accessibilité
À quoi ça sert. Vérifier qu'une page est utilisable par tous, y compris avec un lecteur d'écran ou au clavier seul : champs avec un libellé, images avec un texte alternatif, contrastes suffisants.
Exemple. L'outil axe s'intègre aux tests Playwright (voir la fiche sur les tests de bout en bout) :
AxeResults resultats = new AxeBuilder(page).analyze();
assertThat(resultats.getViolations()).isEmpty();
Il analyse la page et liste les problèmes détectés, avec l'élément concerné et la règle non respectée.
Quand. Pour toute application utilisée par le public. Pour les services publics en France, l'accessibilité est d'ailleurs une obligation légale (le RGAA). Un outil automatique ne détecte pas tout, mais il attrape les erreurs les plus fréquentes à moindre coût.
Les tests d'architecture avec ArchUnit
À quoi ça sert. Vérifier automatiquement les règles de découpage du code que l'équipe s'est fixées, et qui finissent toujours par être contournées « juste cette fois ».
Exemple. « Un contrôleur n'appelle jamais directement un repository : il passe par un service. »
@AnalyzeClasses(packages = "fr.exemple.boutique")
class ArchitectureTest {
@ArchTest
static final ArchRule les_controleurs_passent_par_les_services =
noClasses().that().resideInAPackage("..controller..")
.should().dependOnClassesThat().resideInAPackage("..repository..");
}
Si quelqu'un injecte un repository dans un contrôleur, ce test échoue, avec un message qui nomme la classe fautive.
Quand. Sur les projets de taille moyenne ou grande, où l'équipe veut garder une architecture cohérente dans le temps. Quelques règles simples suffisent.
Les tests de mutation avec PIT
À quoi ça sert. Mesurer si tes tests détectent vraiment les erreurs. La couverture dit quelles lignes sont exécutées par les tests ; la mutation dit si une erreur sur ces lignes serait remarquée.
Le principe. L'outil PIT modifie volontairement ton code, une petite modification à la fois : il remplace un > par un >=, un + par un -, supprime un appel. Chaque version modifiée s'appelle un mutant. Puis il relance les tests :
- si un test échoue, le mutant est tué : tes tests ont détecté la modification ✅ ;
- si tous les tests passent encore, le mutant survit : tes tests n'auraient pas vu cette erreur ❌.
Un mutant qui survit sur montant > PLAFOND signifie qu'aucun test ne vérifie la limite exacte du plafond (voir les tests paramétrés dans la fiche sur les tests unitaires).
Quand. Ponctuellement, sur les classes qui contiennent les règles métier les plus importantes. C'est long à exécuter : on ne le lance pas sur tout le projet à chaque push.
Les tests basés sur les propriétés
À quoi ça sert. Au lieu de choisir soi-même quelques exemples, on décrit une propriété qui doit toujours être vraie, et l'outil génère des centaines de valeurs pour essayer de la mettre en défaut.
Exemple. Avec jqwik :
@Property
void le_total_d_un_panier_n_est_jamais_negatif(@ForAll @Positive int quantite,
@ForAll @Positive int prixEnCentimes) {
Panier panier = panierAvec(quantite, prixEnCentimes);
assertThat(panier.total()).isNotNegative();
}
Si une combinaison fait échouer la propriété (un dépassement de capacité sur de très grandes valeurs, par exemple), l'outil cherche le plus petit exemple qui la casse, et l'affiche.
Quand. Pour des calculs ou des transformations où il est difficile d'imaginer tous les cas : conversions, calculs de montants, analyse de texte. C'est un complément aux tests classiques, pas un remplacement.
Les tests de charge
À quoi ça sert. Vérifier que l'application tient la charge : combien d'utilisateurs simultanés avant que les temps de réponse se dégradent ? Que se passe-t-il au-delà ?
| Variante | La question |
|---|---|
| test de charge | l'application tient-elle la charge attendue (le pic du lundi matin) ? |
| test de stress | jusqu'où peut-on monter avant qu'elle casse, et comment casse-t-elle ? |
| test d'endurance | tient-elle plusieurs heures sans se dégrader (fuite mémoire, connexions qui ne sont pas rendues) ? |
Les outils. Gatling (scénarios écrits en Java) ou k6 (en JavaScript) simulent des centaines d'utilisateurs virtuels. Pendant le test, on observe les métriques de la machine et de l'application (voir la fiche sur les métriques) : c'est leur combinaison qui montre où se trouve la limite (CPU, base de données, pool de connexions).
Quand. Avant une mise en production importante, ou quand une forte affluence est prévue. Toujours sur un environnement représentatif de la production, et jamais sur la production sans l'accord explicite de l'équipe : un test de charge ressemble beaucoup à une attaque.
Les tests de non-régression visuelle
À quoi ça sert. Détecter un changement d'affichage involontaire : un bouton qui se décale, une police qui change, un bloc qui disparaît sur mobile.
Le principe. L'outil prend une capture d'écran de chaque page, et la compare à une capture de référence validée. Toute différence au-delà d'un seuil fait échouer le test, avec une image qui montre ce qui a changé. Si le changement est voulu, on valide la nouvelle capture comme référence.
Quand. Surtout côté front-end, sur des interfaces très travaillées ou des composants réutilisés partout. Playwright en TypeScript le propose directement. Ces tests sont sensibles (polices, tailles d'écran) : ils demandent un environnement d'exécution stable.
Comment choisir ?
Aucun projet n'a besoin de tous ces tests. Pour chacun, la question est la même : ce risque existe-t-il vraiment sur mon projet ?
| Si ton projet… | Pense à… |
|---|---|
| a des utilisateurs et des rôles | les tests de sécurité (presque toujours) |
| a un métier très impliqué dans les spécifications | Cucumber |
| échange avec des applications d'autres équipes | les tests de contrat |
| dépend d'API externes | les tests de résilience |
| a des traitements de nuit ou des imports de fichiers | les tests de batch |
| est utilisé par le public | les tests d'accessibilité |
| grossit et implique plusieurs équipes | ArchUnit |
| contient des règles métier critiques | PIT, ponctuellement |
| attend une forte affluence | les tests de charge |
Et dans tous les cas : ces tests complètent les tests unitaires, d'intégration et de bout en bout. Ils ne les remplacent pas.
À retenir
- Chaque famille de tests cherche un type de défaut précis que les tests classiques ne cherchent pas.
- Les tests de sécurité vérifient qui peut appeler chaque adresse : ce sont les plus souvent utiles.
- Cucumber écrit les tests en phrases lisibles par le métier ; les tests de contrat protègent le format des échanges entre applications.
- Les tests de résilience vérifient la réaction à un service extérieur lent ou en panne.
- ArchUnit protège l'architecture, PIT mesure la qualité des tests, jqwik essaie des centaines de valeurs.
- Les tests de charge mesurent les limites de l'application, sur un environnement représentatif et jamais en production sans accord.
- On choisit ces tests selon les risques réels du projet, pas pour les cocher tous.