Quand la revue de code ne suffit plus
Imaginez la situation : une règle d'équipe simple, documentée dans l'onboarding, présente dans la checklist de revue de code, connue de tous. Et pourtant, trois semaines après l'arrivée d'un nouveau développeur, cette règle est oubliée sur un nouveau modèle. Le code métier est propre, bien testé, la pull request passe en revue sans accroc... sauf qu'un trait essentiel à l'isolation des données entre clients (tenants) manque.
Résultat : pendant deux jours, un client a pu apercevoir des fragments de données appartenant à un autre client, dans un rapport spécifique. Ce n'est pas un audit de sécurité qui a détecté le problème, mais un ticket de support. Les tests automatisés, eux, n'avaient rien vu.
Cette histoire, racontée par Sheikh Shahzaman sur dev.to (article original ici), illustre une vérité que nous connaissons bien chez MulerTech : l'erreur humaine n'est pas une question de compétence, c'est une question de probabilité. Plus une équipe grandit, plus le nombre de modèles, de contrôleurs et de services augmente, et plus la probabilité qu'une règle architecturale soit oubliée quelque part tend vers 1.
La bonne nouvelle, c'est qu'il existe une catégorie de tests souvent sous-utilisée qui permet de transformer une règle d'équipe en garde-fou automatique : les tests d'architecture.
Tester la structure, pas seulement le comportement
La quasi-totalité des tests que l'on écrit au quotidien sont des tests de comportement : on donne une entrée à une fonction, on vérifie la sortie. C'est indispensable, mais cela ne dit rien sur la manière dont le code est organisé.
Un test d'architecture, lui, pose des questions structurelles :
- Est-ce que toutes les classes d'un dossier
Modelsimplémentent bien une interface obligatoire ? - Est-ce qu'un namespace
App\Domainne dépend jamais directement d'une classeApp\Infrastructure? - Est-ce que tous les modèles stockant des données spécifiques à un client utilisent bien le trait d'isolation prévu pour ça ?
Dans l'écosystème PHP, l'outil Pest popularise cette approche grâce à sa fonction arch(). Voici l'exemple qui a permis de détecter (rétroactivement) la fuite décrite dans l'article source :
// tests/Architecture/ArchTest.php
arch('tenant models must use the BelongsToTenant trait')
->expect('App\Models')
->toUseTrait('App\Traits\BelongsToTenant')
->ignoring('App\Models\SystemSetting');
Cinq lignes. C'est tout ce qu'il a fallu pour transformer une règle informelle en règle exécutable. Si un développeur ajoute un modèle sans le trait BelongsToTenant, ce test échoue immédiatement, en local et en CI, avant que le code n'atteigne la production.
L'approche n'est pas spécifique à Laravel. Dans un projet Symfony, le même type de garantie peut être obtenu avec des outils comme PHPArkitect, Deptrac ou des tests Pest adaptés via une couche de réflexion, en ciblant par exemple les entités Doctrine plutôt que les modèles Eloquent. Le principe reste identique : décrire la règle métier sous forme de contrat structurel, puis laisser la machine vérifier ce contrat à chaque commit.
Pourquoi la revue de code humaine ne suffit pas
Il est tentant de se dire que ce genre d'incident est dû à un manque de vigilance du relecteur. Ce serait une mauvaise lecture du problème.
Une revue de code humaine est, par nature, une analyse locale et contextuelle. Le relecteur regarde si la logique métier de la pull request fait ce qu'elle doit faire. Il vérifie les noms de variables, la gestion des erreurs, la cohérence avec les conventions du projet. Mais il ne peut pas, à chaque revue, recharger mentalement l'ensemble des règles architecturales du projet et les vérifier une par une sur chaque fichier modifié — surtout quand ces règles ne sont pas directement visibles dans le diff.
Dans le cas du trait BelongsToTenant, le code de la pull request était correct en lui-même. Rien dans la classe ne signalait visuellement qu'il manquait quelque chose, car ce qui manquait, c'était justement... une absence. Et les absences sont notoirement difficiles à repérer pour un humain, surtout sous pression de deadline ou avec plusieurs PR à relire dans la journée.
La revue de code reste essentielle pour évaluer la qualité, la lisibilité et la pertinence métier d'un changement. Mais pour les règles non négociables — celles dont la violation peut entraîner une fuite de données, un problème de conformité ou une faille de sécurité — il faut un mécanisme qui ne dépend ni de la fatigue, ni de l'expérience, ni de l'attention d'une personne donnée un jour donné.
Comment MulerTech intègre ces garde-fous dans vos projets
Chez MulerTech, lorsque nous concevons une architecture multi-tenant ou multi-organisation pour un client, l'isolation des données n'est jamais traitée comme une simple convention de code. Nous la traitons comme une contrainte architecturale qui doit être vérifiable automatiquement, au même titre que les tests unitaires ou les tests fonctionnels.
Concrètement, cela se traduit par plusieurs réflexes que nous intégrons dans nos pipelines CI/CD :
- Formaliser les règles critiques en tests d'architecture dès qu'une convention dépasse le simple "il faudrait que". Un trait obligatoire, un scope global, une interface à implémenter, une dépendance interdite entre couches : tout cela peut devenir une assertion testable.
- Bloquer le merge en cas d'échec. Un test d'architecture qui passe au vert n'a de valeur que s'il est exécuté à chaque commit et empêche la fusion en cas d'échec, exactement comme un test unitaire qui casse.
- Documenter la règle là où elle vit. Le test d'architecture devient lui-même la documentation : un nouveau développeur qui essaie de comprendre pourquoi son modèle ne compile pas trouve la réponse directement dans le message d'échec, sans avoir à chercher dans un wiki d'onboarding qu'il n'a peut-être pas lu en entier.
- Étendre progressivement la couverture. On ne formalise pas toutes les règles le premier jour. On commence par les plus critiques (isolation des données, sécurité, séparation des couches) et on enrichit la suite à chaque incident ou revue d'architecture, exactement comme l'équipe de l'article source l'a fait après son incident.
L'enseignement principal de l'histoire racontée sur dev.to n'est pas "ce développeur a fait une erreur", mais "ce type d'erreur était prévisible et automatisable". Une règle qui dépend uniquement de la mémoire collective d'une équipe est une règle qui sera, statistiquement, oubliée tôt ou tard. Une règle encodée dans un test d'architecture est une règle qui ne peut tout simplement pas être contournée par un commit qui passe la CI.
Conclusion
L'erreur humaine n'est pas un échec individuel, c'est une variable systémique avec laquelle toute équipe en croissance doit composer. La vraie question n'est pas "comment éviter que ça arrive", mais "comment garantir que, même si ça arrive, ça ne puisse pas atteindre la production".
Les tests d'architecture, qu'ils soient écrits avec Pest pour un projet Laravel ou avec des outils équivalents pour un projet Symfony, offrent exactement ce filet de sécurité : ils transforment une règle de bonnes pratiques en barrière technique, vérifiée automatiquement à chaque commit.
Si vous gérez une application multi-tenant, multi-client ou multi-organisation, et que vous voulez vous assurer que l'isolation des données de vos utilisateurs repose sur autre chose qu'une checklist humaine, l'équipe MulerTech peut vous aider à auditer votre architecture existante et à mettre en place ces garde-fous dans votre pipeline CI/CD.
Cet article s'inspire de la publication originale "How a Five Line Architecture Test Caught a Data Leak a Code Review Missed" de Sheikh Shahzaman, publiée sur dev.to.