Lemme 1
Insurance Billing QA Framework
Automatisation de tests de bout en bout pour une plateforme simulée de facturation d'assurance : une application Spring Boot et les sept niveaux de tests qui la tiennent à ses règles.

Aperçu
Le dépôt comporte deux moitiés volontairement séparées. La première est le système testé : une application de facturation Spring Boot qui gère clients, polices, factures, calendriers de versements et un grand livre de transactions en ajout seul, exposée par une API REST, un service SOAP défini par contrat et une console Web en anglais et en français. La seconde est l'automatisation qui la met à l'épreuve à sept niveaux — unitaire, intégration, API, interface, BDD, tests de fumée et performance — ainsi que le pipeline qui l'exécute à chaque demande de tirage.
J'ai réalisé les deux moitiés. Le domaine de la facturation est simulé; l'automatisation, le pipeline et les anomalies découvertes en cours de route sont bien réels.
Problème
Il est facile de démontrer l'automatisation des tests sur une page jouet, et difficile de bien la pratiquer sur un vrai système. Les difficultés sont justement celles qu'une petite démo masque : à qui appartiennent les données de test, comment attendre un navigateur sans temporisation fixe, comment un pipeline démarre et arrête une véritable application, et comment les chiffres de la documentation restent exacts à mesure que la suite grandit.
Je voulais un système dont les règles ont de bonnes réponses. Les montants doivent concorder au cent près, les factures passent d'un état à l'autre, et un paiement peut être refusé pour une raison légitime : chaque test vérifie donc quelque chose qui compte.
Objectifs
- Vérifier les mêmes règles de facturation à plusieurs niveaux, des tests unitaires aux tests dans le navigateur, et pouvoir dire quel niveau détecte quoi.
- Garder l'automatisation indépendante du code de l'application, pour qu'elle teste le contrat publié et non des classes partagées.
- Exécuter les suites à chaque demande de tirage, sur l'artefact exact qui vient d'être construit.
- Ne publier que des chiffres mesurés, et signaler clairement tout ce qui n'a jamais été exécuté.
Technologies
| Application testée | Java 25, Spring Boot 3.5, Spring Data JPA, Thymeleaf, Bean Validation, H2 |
|---|---|
| Automatisation des tests | JUnit 5, TestNG, REST Assured, Selenium 4, Cucumber 7, Cypress 15, AssertJ, JMeter |
| Build et intégration continue | Maven, GitHub Actions, JaCoCo, SonarQube Cloud, Codecov |
Architecture
Un build Maven multi-module. billing-app est l'application. qa-api-tests, qa-ui-tests et qa-bdd-tests sont les modules d'automatisation, accompagnés d'une suite de fumée Cypress et d'un plan JMeter.
Les modules d'automatisation ne dépendent aucunement du code de l'application. Ils ne l'atteignent que par HTTP et par la page affichée. Si les deux côtés partageaient une énumération, renommer une constante modifierait le test et l'application du même coup, et la suite resterait au vert alors que le contrat aurait été rompu en silence.
Le code du cadre de test — clients d'API, objets de page, générateurs de données — se trouve dans le src/main/java de chaque module, et les tests dans src/test/java. Le module BDD réutilise ainsi les clients et les objets de page comme de simples classes de bibliothèque, si bien que ses définitions d'étapes ne contiennent aucune logique d'automatisation.
Automatisation
- qa-api-testsREST Assured, TestNG
- qa-ui-testsSelenium 4, objets de page
- qa-bdd-testsCucumber 7
- cypressTests de fumée
- perfPlan JMeter
Contrat publié
- REST API
- SOAPDéfini par contrat
- Web consoleAnglais et français
billing-app
- DomainRègles de facturation dans les entités
- ServicesRecherche et transactions
- JPA
Stockage
- H2En mémoire, initialisée au démarrage
Mes responsabilités
Il s'agit d'un projet individuel. J'ai conçu et écrit l'application, toutes les suites de tests, le pipeline et la documentation.
- Conception du domaine de facturation et mise en œuvre avec Spring Boot : API REST, service SOAP et console Web bilingue.
- Construction des cadres d'automatisation API, interface et BDD, ainsi que d'une suite de fumée Cypress et d'un plan de charge JMeter.
- Écriture du pipeline GitHub Actions à six tâches, y compris la couverture fusionnée d'une tâche à l'autre.
- Rédaction de la stratégie de test, de 34 cas de test manuels, d'une matrice de traçabilité pour 33 exigences et de 14 rapports d'anomalie.
Tous les commits du dépôt sont les miens. Comme sa documentation le dit sans détour, cela signifie aussi que le code a été relu par son auteur, et non par des pairs.
Décisions d'ingénierie
Les règles de facturation vivent dans l'entité
Une vérification placée dans un service est contournée par tout autre appelant qui atteint l'entité; une vérification à l'intérieur de Invoice.applyPayment ne l'est pas. Les règles deviennent aussi testables en simple JUnit, sans contexte Spring. Elles sont évaluées de la moins coûteuse à la plus coûteuse — montant, état de la facture, état de la police, puis trop-perçu — et un test fige cet ordre. Le prix à payer : une entité plus volumineuse qu'un modèle anémique.
Un paiement nul donne 422, pas 400
L'objet de requête ne valide que la présence des champs. Qu'un montant soit positif, correctement arrondi ou inférieur au solde dû relève d'une règle de facturation, signalée par un 422 et un code de motif. Sans cette distinction, un test ne pourrait pas différencier « le client a envoyé n'importe quoi » de « la plateforme a appliqué une règle », et une régression transformant l'un en l'autre passerait inaperçue.
L'argent n'est jamais arrondi en silence
Les montants sont des BigDecimal à deux décimales, normalisés avec RoundingMode.UNNECESSARY : une valeur qui exigerait un arrondi lève une exception plutôt que de perdre un cent. Trois versements de 33,33, 33,33 et 33,34 règlent exactement une facture de 100,00, et un montant trop précis comme 10,001 est refusé.
Uniquement des attentes explicites
Le module d'interface ne contient ni attente implicite ni Thread.sleep. Mélanger attentes implicites et explicites produit des délais d'expiration qui n'appartiennent à aucune des deux. Chaque session de navigateur est conservée dans un ThreadLocal, puis fermée et retirée, car TestNG réutilise les fils d'exécution et une entrée périmée remettrait une session morte au test suivant.
Le pipeline teste l'artefact qu'il a construit
La première tâche construit le jar et le publie; les tâches API, interface, BDD et fumée téléchargent ce jar et s'exécutent en parallèle : elles testent donc l'artefact validé, et non une seconde compilation du même code. Elles démarrent l'application avec le script qu'un développeur lance sur son poste, et interrogent son point de contrôle de santé plutôt que de patienter un délai fixe.
Les décomptes de tests sont générés, pas saisis
Un script extrait les décomptes des rapports de la dernière exécution et produit un inventaire des tests; un autre fait échouer le build si un chiffre cité dans la documentation le contredit. C'est le correctif d'une anomalie que j'ai consignée contre ma propre documentation, où huit chiffres tenus à la main avaient divergé.
Tests et qualité
600 tests automatisés : 176 tests unitaires du domaine et des services, 160 tests d'intégration Spring, 129 tests d'API, 67 tests d'interface, 53 scénarios BDD et 15 tests de fumée Cypress. 596 d'entre eux s'exécutent à chaque demande de tirage; les quatre exclus réinitialisent la base de données et doivent donc s'exécuter seuls.
Chaque test qui modifie des données crée son propre client, sa propre police et sa propre facture au moyen de l'API publique. Aucune suite modificatrice ne touche aux enregistrements initiaux, ce qui rend l'exécution en parallèle sûre.
La couverture est mesurée sur l'ensemble de la pile. Les suites API, interface et BDD pilotent l'application dans un processus distinct : une mesure ordinaire, dans le même processus, ne voit rien de ce qu'elles exercent. Le pipeline attache l'agent JaCoCo à l'application en cours d'exécution et fusionne les résultats dans une dernière tâche. Aucun seuil de couverture n'est imposé, et c'est voulu.
En complément de l'automatisation : 34 cas de test manuels, chacun marqué comme automatisé ou non, et une matrice reliant 33 exigences aux méthodes de test qui les couvrent.
Défis
Une exécution au vert qui testait le mauvais serveur
Une ancienne instance occupait encore le port 8080. Le nouveau jar n'a pas pu s'y lier et s'est arrêté, la sonde de santé a reçu la réponse de l'ancien processus, et le script de démarrage a déclaré l'application en bon état. La suite SOAP a ensuite échoué avec des 404 contre un jar antérieur au service SOAP, ce qui a orienté l'enquête vers la configuration des servlets plutôt que vers le port. Le correctif refuse de démarrer si le port est déjà servi, et vérifie que le processus est vivant avant de vérifier son état de santé.
En bon état avant que les données existent
L'initialisation des données s'exécutait après le démarrage du serveur Web : l'application acceptait donc des requêtes et se déclarait en bon état avec une base vide — précisément la fenêtre où arrive chaque suite : attendre l'état de santé, lire une facture, obtenir un 404 intermittent. L'initialisation a été déplacée à la création des beans, dans un bean distinct afin de passer malgré tout par le mandataire transactionnel.
Une anomalie invisible malgré une couverture complète
Qu'une facture soit en retard ou non dépendait du fuseau horaire par défaut de la machine. Aucun test ne pouvait le détecter, puisque chaque test évaluait la règle dans le même fuseau que le code. C'est l'analyse statique qui l'a trouvée, dans du code déclaré entièrement couvert. Le fuseau de facturation est désormais un paramètre de configuration, et le test de non-régression fige une horloge à un instant où l'UTC et le fuseau d'affaires ne s'entendent pas sur la date.
Deux mauvaises façons d'attendre une navigation
Attendre une condition sur la page de destination revenait immédiatement, puisque le même élément était déjà à l'écran. Attendre qu'un ancien élément devienne périmé était juste en principe, mais échouait sur l'exécuteur d'intégration continue, où Chrome lève parfois une autre erreur en pleine navigation. La version qui tient pose un marqueur sur window avant l'action, puis attend sa disparition et le chargement complet du document. À aucun moment la réponse n'a été une temporisation ou une nouvelle tentative.


Résultats
| Mesure | Résultat | Source |
|---|---|---|
| Tests automatisés | 600, tous réussis; 596 à chaque demande de tirage | README du dépôt, version 1.2.0 |
| Couverture, pile complète | 98,4 % des lignes, 87,6 % des branches | README du dépôt, version 1.2.0 |
| Analyse statique | Seuil de qualité SonarQube Cloud franchi : 0 bogue, 0 vulnérabilité | README du dépôt, version 1.2.0 |
| Anomalies trouvées et corrigées | 14, dont quatre dans l'infrastructure de test | Journal des anomalies et inventaire des tests |
| Exigences reliées à des tests | 33 | Matrice de traçabilité |
| Intégration continue | 6 tâches; dernière exécution sur main réussie | GitHub Actions, vérifié le 12 octobre 2026 |
| Test de charge | 1 050 échantillons, 0 % d'erreurs | Exécution JMeter du 15 septembre 2026 |
Tableau 1. Ces chiffres sont tirés du dépôt, qui génère ses décomptes de tests à partir de la dernière exécution et fait échouer le build lorsque la documentation les contredit.
Ce que cela ne démontre pas
- Les chiffres du test de charge ne mesurent pas une capacité : le générateur de charge partageait la machine de l'application, la base de données était en mémoire et l'exécution n'a duré que quelques secondes.
- Le dépôt contient un Jenkinsfile qui n'a jamais été exécuté sur un contrôleur Jenkins. Le pipeline réellement utilisé est celui de GitHub Actions.
- La base de données est une H2 en mémoire, et les paiements simultanés sur une même facture ne sont pas testés.
Dépôt et liens
abdeljalilsennaoui/insurance-billing-qa-framework(ouvre un nouvel onglet)
Ce projet n'a pas de démo hébergée. Le dépôt explique comment l'exécuter.
Fin de l'étude de cas