Développement d’applications9 min de lecture

Étude de cas : CI/CD et tests unitaires pour trois applications bancaires en marque blanche en dix semaines

Comment Mobian Studio a fait passer un SDK fintech et trois applis bancaires en marque blanche à une CI/CD unique, et presque triplé les tests unitaires du SDK.

La plupart des articles sur la CI/CD décrivent ce à quoi un pipeline devrait ressembler. Celui-ci décrit un pipeline que nous avons réellement construit, pour une plateforme fintech dont le SDK Android fait tourner trois applications bancaires en marque blanche.

À notre arrivée, aucun des six dépôts n’avait de CI fonctionnelle. Les builds étaient faits à la main, les numéros de version étaient incrémentés à la main dans des commits, et une bibliothèque n’existait que sur la machine d’un seul développeur. Dix semaines plus tard, le SDK et trois applications clientes partageaient un même système CI/CD, chaque build de test pouvait être reproduit à partir d’un tag Git, et la suite de tests unitaires du SDK avait presque triplé.

Dans cet article, nous passons en revue le point de départ, la conception du pipeline, la stratégie de test, les résultats datés et le travail qui reste à faire.


Le point de départ

Le client est une plateforme fintech qui opère plusieurs applications bancaires et de paiement. La famille de produits comprend :

  • Un SDK Android d’environ 29 modules Gradle, qui transforme le JSON du backend en écrans natifs et gère aussi les paiements, la biométrie et la lecture de cartes.

  • Trois applications bancaires en marque blanche construites sur ce SDK, chacune avec son propre modèle de branches et de publication.

  • Une bibliothèque réseau et une suite de tests end-to-end gérée par la QA.

En juillet-août 2026, la situation était la suivante :

  • D’anciens templates CI dormaient dans le dépôt du SDK, mais rien ne tournait.

  • Les version codes étaient incrémentés à la main : environ 95 commits de ce type dans une application depuis 2025, et 28 dans une autre.

  • Un build ne pouvait pas être reproduit à partir d’un checkout propre, car une bibliothèque ne vivait que dans le cache Maven local d’un développeur.

  • Certains modules de test du SDK ne compilaient même pas : la toute première exécution de la CI l’a révélé.

  • Le SDK comptait 1 122 tests exécutés et 8,1 % de couverture totale ; l’application principale, 56 tests.

📌 À retenir :

Personne dans l’équipe ne faisait quoi que ce soit de travers. Sans pipeline, le savoir-faire de publication vit simplement dans la tête des gens et sur leurs ordinateurs portables, et dans un domaine bancaire réglementé, c’est un risque en soi.


Les contraintes

Le pipeline devait s’adapter à l’infrastructure réelle du client, et non à une infrastructure idéale :

  • Un GitLab auto-hébergé, migré depuis gitlab.com en juillet 2026.

  • Un seul runner macOS partagé, sans Docker, utilisé aussi par d’autres projets de l’entreprise, avec un espace de travail qui persiste entre les jobs.

  • Des builds à cheval sur deux dépôts : chaque build d’application combine une branche de l’application et une branche du SDK qui n’est pas forcément encore mergée.

  • Un domaine bancaire réglementé, qui pousse vers des builds reproductibles, une signature de production tenue hors de Git et l’analyse des dépendances.


Ce que nous avons construit : un pipeline pour trois applications

1. Un formulaire web jusqu’aux testeurs

Un build de test part d’un simple formulaire GitLab : on choisit la branche de l’application, la branche du SDK, le type de build et les groupes de testeurs. Le pipeline enchaîne alors six étapes :

  1. Prepare : résout la branche du SDK en un commit unique, pour que tous les jobs suivants utilisent exactement le même code du SDK.

  2. Test : tests unitaires de l’application et du SDK, couverture et analyse des dépendances.

  3. Build : un APK signé (plus l’AAB pour le Play Store lors des publications), avec des identifiants de signature récupérés dans les variables CI.

  4. Distribute : Firebase App Distribution vers les groupes de testeurs sélectionnés.

  5. Notify : un commentaire Jira sur chaque ticket inclus, avec le lien du build et la couverture, et un passage au statut « Ready for QA ».

  6. Tag : le même tag sur le commit de l’application et sur celui du SDK.

2. Chaque build de test est reproductible

Comme le tag est posé dans les deux dépôts après la distribution, un tag signifie « ce build exact est arrivé chez les testeurs ». Il suffit d’extraire ce tag dans les deux dépôts pour reproduire le build. Auparavant, c’était tout simplement impossible.

3. Un seul template, des portages mécaniques

Les trois applications exécutent le même pipeline en six étapes. Seules quelques constantes diffèrent (label Jira, préfixe de tag, groupes de testeurs), et elles sont regroupées dans un job template caché. La deuxième et la troisième application ont été portées en quelques jours, alors que l’une d’elles utilise un modèle de branches de release et présentait sept blocages distincts à lever.

4. Des contrôles qualité là où l’équipe travaille déjà

  • Les résultats des tests dans l’onglet Tests de la merge request.

  • Un badge de couverture de la logique dans le widget de la merge request, et des marqueurs de couverture sur les lignes modifiées.

  • SonarQube sur la branche principale.

  • OWASP Dependency-Check avec une SBOM CycloneDX dans l’application principale.

5. Un runner partagé traité comme non fiable

Les secrets ne vivent que dans les variables CI, rien n’est stocké sur le runner, chaque job clone le SDK dans son propre espace de travail (après que des jobs parallèles ont un jour supprimé mutuellement leur checkout), et les builds de production signés contournent toujours le cache de build.


Ce que nous avons construit : une stratégie de test qui commence par l’architecture

La couverture servait d’étalon, mais le vrai travail consistait à rendre le code testable. Quatre décisions ont façonné le résultat.

1. Mesurer le code qui compte

Nous avons défini un périmètre logique : le domaine, les données, les utilitaires non Android, les moteurs de requêtes et d’événements, et les ViewModels de base. Le code d’interface, le câblage de l’injection de dépendances et le code généré restent mesurés, mais sans objectif. Cela seul a fait passer le point de départ honnête de 8 % (tout le code) à environ 24 % (la logique).

2. Verrouiller l’architecture par effet cliquet

Sept règles d’architecture automatisées s’exécutent comme des tests unitaires : par exemple, aucun type Android dans le code de logique et aucune injection de dépendances globale. Les violations actuelles sont figées dans un fichier de référence : une nouvelle violation fait échouer le build, tout comme une violation corrigée tant qu’elle n’a pas été retirée du fichier de référence. La dette ne peut que diminuer.

3. Les points de découplage d’abord, les tests ensuite

Chaque dépendance Android ou fournisseur (analytics, permissions, biométrie, cache d’écrans, signature des requêtes) a reçu une petite interface dans la couche domaine et un adaptateur hors du périmètre de couverture. Dès lors, la logique s’exécute sur la JVM standard en quelques millisecondes, et les tests deviennent peu coûteux à écrire.

4. Figer le comportement plutôt que le corriger en douce

Les tests de la couche données enregistrent ce que fait le code aujourd’hui. Chaque comportement douteux devient une question adressée au produit ou au backend au lieu d’un changement silencieux : 76 questions ouvertes à ce jour. Quand une décision tombe, le test de caractérisation échoue volontairement et est mis à jour en même temps que le correctif.


Les résultats

Tous les chiffres ci-dessous datent du 5 octobre 2026 ; ils proviennent de l’API des pipelines GitLab ou ont été comptés dans les dépôts.

Indicateur Avant Après
Dépôts avec une CI fonctionnelle 0 sur 6 4 sur 6 (SDK + 3 applications)
Du merge aux testeurs Builds manuels, incréments de version manuels Environ 6 à 8 minutes, entièrement automatisé
Builds de test passés par le pipeline — 101 depuis le 8 septembre
Pipelines exécutés — Environ 530
Tests unitaires du SDK 1 141 (11 septembre) 3 246 (×2,8)
Couverture de la logique du SDK Environ 24 % 70,9 %
Les tests en échec bloquent les merges Non Oui, dans le SDK et l’application principale
Imports Android dans le code de logique 298 192 (−36 %)
Reproduire un build de test Impossible Extraire un tag dans les deux dépôts

La période la plus rapide : la couverture de la logique est passée de 30,7 % à 70,9 % en sept jours (du 28 septembre au 5 octobre), lorsque toute la couche données a été couverte au fil de 13 tickets. Ce bond n’a été possible que parce que les points de découplage de l’architecture étaient déjà en place.

Les tests se sont aussi rentabilisés avant même que l’objectif de couverture soit atteint. L’écriture des tests de caractérisation a mis au jour des crashs sur des données serveur mal formées, des lacunes de compatibilité ascendante dans d’anciennes versions de l’application et une boucle de nouvelles tentatives de rafraîchissement de token trop agressive, autant de points désormais suivis et priorisés.

Ce qui reste à faire

Nous préférons publier les manques à côté des résultats :

  • Pas encore de seuil de couverture : les merges sont bloqués par des tests en échec, pas par une baisse de couverture. Les jalons de 40 % et 60 % sont franchis ; celui de 80 % reste à atteindre.

  • Deux applications ont encore des tests non bloquants et n’ont pas adopté la boîte à outils de test commune.

  • Deux dépôts n’ont pas de CI, et les tests end-to-end tournent toujours sur une machine locale.

  • Un runner unique est un point de défaillance unique : deux jours différents, des jobs ont attendu jusqu’à expiration du délai pendant qu’il était indisponible. Un deuxième runner ou un pool à mise à l’échelle automatique est la prochaine étape.


La méthode que nous appliquons désormais avec d’autres clients

  1. Figer les dépendances partagées et taguer chaque dépôt impliqué dans un build, pour que n’importe quel build de test puisse être reproduit.

  2. Utiliser un seul template de pipeline par famille de produits, en réduisant les différences à une poignée de constantes.

  3. Boucler la boucle là où l’équipe travaille déjà : le lien du build et la couverture vont sur le ticket Jira et dans la merge request.

  4. Redéfinir la couverture avant de la poursuivre, pour que le chiffre soit honnête et l’objectif atteignable.

  5. Procéder par effet cliquet, pas par big bang : figer les violations actuelles et faire échouer tout ce qui est nouveau.

  6. Traiter un runner partagé comme non fiable : secrets dans les variables CI, builds de production jamais mis en cache.

Conclusion

En dix semaines, une famille de produits fintech est passée d’APK compilés à la main à un système CI/CD unique : environ 530 pipelines, 101 builds de test automatisés, du merge aux testeurs en quelques minutes, et une suite de tests du SDK passée de 1 141 à 3 246 tests, avec une couverture de la logique de 70,9 %. Rien de tout cela n’a nécessité un nouveau fournisseur de CI : seulement un pipeline conçu autour de l’infrastructure réelle du client et une architecture qui permet les tests.

Chez Mobian, c’est le genre de travail que nous préférons montrer plutôt que décrire. Si vos publications dépendent encore de l’ordinateur portable d’une seule personne, nous pouvons commencer par un audit de votre processus actuel de build et de publication, et vous dire honnêtement ce qui mérite d’être automatisé en priorité.

Questions fréquentes

Combien de temps la mise en place de la CI/CD a-t-elle pris ?

Environ dix semaines, de fin juillet à début octobre 2026. Le premier pipeline SDK au vert a tourné au bout d’un mois environ. Les builds de test via le pipeline ont commencé début septembre. Ensuite, porter ce même pipeline sur les autres applications bancaires a pris quelques jours, et non plusieurs semaines.

Pourquoi mesurer la couverture de la logique plutôt que la couverture totale ?

La couverture totale inclut le code d’interface, le câblage de l’injection de dépendances et le code généré, où les tests unitaires apportent peu. La couverture de la logique ne compte que le code du domaine, des données et des moteurs, là où les bugs coûtent de l’argent. Le chiffre devient honnête et l’objectif atteignable. Dans ce projet, le point de départ était de 8,1 % de couverture totale, mais d’environ 24 % de couverture de la logique, avant même l’écriture du moindre nouveau test.

Le pipeline tournait-il sur une CI cloud ?

Non. Le client utilise un GitLab auto-hébergé avec un seul runner macOS partagé, et le pipeline a été conçu en conséquence : chaque job dispose de son propre checkout du SDK, les secrets ne vivent que dans les variables CI, et les builds de production signés ne sont jamais repris du cache de build partagé.

Le travail est-il terminé ?

Pas entièrement, et nous préférons le dire. Le seuil de couverture (bloquer un merge quand la couverture baisse) n’est pas encore en place, les tests de deux des applications ne sont toujours pas bloquants, et le runner unique reste un risque de capacité et de disponibilité. Le jalon de 80 % de couverture de la logique reste à atteindre.

Cette approche peut-elle fonctionner pour un produit iOS ou cross-platform ?

Les principes se transposent : figer les dépendances partagées, taguer chaque build de test, mesurer le code qui compte et ajouter des tests une fois que l’architecture le permet. Ce projet-ci ne concernait qu’Android ; sur iOS, les outils diffèrent (Xcode, Swift Package Manager, XCTest), mais la méthode reste la même.

Estimation gratuite · sans engagement

Vous prévoyez un projet sur cette stack ?

Nous avons travaillé avec les outils dont nous parlons. Présentez-nous votre projet et obtenez un deuxième avis d’ingénieurs qui les ont mis en production.

  • 1Nous passons en revue votre produit, votre stack et votre roadmap
  • 2Nous vous signalons les risques déjà rencontrés sur des projets similaires
  • 3Vous recevez une proposition d’équipe et un calendrier indicatif sous 48 heures

Parlez-nous de votre projet

Vous préférez d’abord en parler ? Réservez un appel de 30 minutes