Tester du "Legacy Code", mission impossible?
Certaines organisations tentent d'ajouter des tests unitaires automatisés au code existant pour stabiliser un système en production. Les développeurs se frappent rapidement à plusieurs problèmes, le système n’ayant pas été conçu au départ pour accepter ces tests. Nous verrons des exemples typiques de problèmes rencontrés et leurs solutions, ainsi que les causes produisant code patrimonial.
À propos de Karl Métivier
Monsieur Métivier détient un baccalauréat en informatique de génie, obtenu en 1997, à l'Université Laval. Il possède plus de dix-neuf (19) années d'expérience en développement de systèmes informatiques dans diverses technologies, dont le Microsoft.NET.
Passionné de code, d’architecture et des approches Agiles, M. Métivier a toujours le souci d’écrire le code de manière lisible, simple et bien organisée. Il applique les principes de « Clean Code » et « SOLID » autant que possible.
L’expérience de Monsieur Métivier couvre plusieurs domaines de l’informatique et en particulier l'architecture logicielle, la programmation et les méthodes Agiles. Il connait d’ailleurs très bien SCRUM, XP Programming, Kanban et Lean Software Development. Il est de plus un ScrumMaster certifié (CSM) depuis 2008 et Professionnal Scrum Master (PSM I) depuis 2014. Il occupe aussi parfois le rôle de Coach Agile pour expliquer des concepts, faire des présentations et guider les équipes dans leur transition Agile.
2. Bon après-midi, Ethan.
• Votre mission, si vous l’acceptez, consiste à vous
occuper d’un système informatique qui n’est pas
nouveau ni trop vieux, mais qui n’a aucun tests
automatisé.
• Par contre, il est au cœur de plusieurs traitements
d’affaires critiques. Vous allez avoir à ajouter de
nouvelles fonctionnalités et a en faire l’entretien.
• Nous avons observés les choses suivantes:
– Classes de la logique d’affaire qui fait 32 000 lignes.
– Une interface de 2000 lignes (incluant une ligne de
commentaire par méthode) été créé récemment pour vous
permettre d’introduire des tests.
• Ce message s’auto-détruira dans 5 secondes …..
3. Bon après-midi, Ethan.
• Votre mission, si vous l’acceptez, consiste à vous
occuper d’un système informatique qui n’est pas
nouveau ni trop vieux, mais qui n’a aucun tests
automatisé.
• Par contre, il est au cœur de plusieurs traitements
d’affaires critiques. Vous allez avoir à ajouter de
nouvelles fonctionnalités et a en faire l’entretien.
• Nous avons observés les choses suivantes:
– Classes de la logique d’affaire qui fait 32 000 lignes.
– Une interface de 2000 lignes (incluant une ligne de
commentaire par méthode) été créé récemment pour vous
permettre d’introduire des tests.
• Ce message s’auto-détruira dans 5 secondes …..
MISSION
4. Qui sommes-nous ?
• Karl Métivier
– Architecte Logiciel
– Développeur
– Coach Agile
– Formateur
• Yves St-Hilaire
– Soutien au développement
– Accompagnement, mentorat
– Développement BI
5. Histoire d’un système
• On change notre processus, on passe
à Scrum.
• On nous demande d’ajouter des tests
unitaires automatisés au code
existant.
– Oups, le système n’a pas été prévu
pour cela…
• Comment avoir le temps pour cela ?
– Réunion pour discuter des bogues
courants.
– Documentation à mettre à jour.
• Au final, problème de dette
technique.
7. Définitions pour le Legacy Code
• Du code Legacy, c’est du code:
– sans tests unitaires - Michael Feathers
– plus vieux que ceux qui y travaillent
– modifications en mode « patchage »
– nécessitant un guide du marais pour
s’y retrouver!
• Enfreint constamment les principes
SOLID
• On n’ose pas y toucher.
• Et les Legacy Systems ?
8. Code « Legacy »
Quand vous avez à
travailler avec ça, c’est
quoi votre feeling ?
9. Pourquoi est-on pognés avec ça ?
• Délais courts de développement
• Plusieurs personnes y sont passées
• Conception Legacy?
–Architecture identique au dossier fonctionnel
• Peu d’efforts alloués pour l’amélioration
–Le système fonctionne, on n’y investit rien
17. Cas 1 – Classe sans injection de dépendances
• Pas de possibilité d’injecter un mock
• Plusieurs appelants l’utilisent, souvent on ne
peut les modifier
18. Cas 1 – Classe sans injection de dépendances
Comment ?
• Pour la classe qu’on veut mocker :
– Ajout d’une interface
• À la classe à tester :
– Ajout d’un constructeur permettant l’injection
– On lui injectera l’instance à utiliser, ou une Factory
• Les appelants ne sont pas impactés
• On permet l’injection, mais les appelants existants
ne l’utilisent pas
19. Cas 1 – Ajout d’injection – Comment
public class MyClass {
private IFileReader reader;
public MyClass(string fileName) {
// original code
// ...
reader = new MyFileReader(fileName);
}
public MyClass(IFileReader injectedReader) {
// original code
// ...
reader = injectedReader;
}
}
20. Cas 1b – Classe sans injection de dépendances
Comment ?
• Autre technique : Patron « Test Specific Subclass »
• Classe à mocker : ajout d’une interface à la classe
• Classe à tester :
– Ajout d’une méthode virtuelle
« ObtenirInstanceClasseAppelee »
• Dans le projet de test :
– Hériter de la classe à tester
– Implémenter notre propre
« ObtenirInstanceClasseAppelee »
21. Cas 1b – Test specific subclass – Comment?
public class MyClass {
protected int someProtectedInfo = 42;
private IFileReader reader;
public MyClass(string fileName) {
reader = GetReader(fileName);
}
protected virtual IFileReader GetReader(string fileName) {
return new MyFileReader(fileName);
}}
public class MyClassSpecificSubclass : MyClass {
public IFileReader InsertMockHere;
public MyClassSpecificSubclass(IFileReader aReader) {
InsertMockHere = aReader;
}
protected override IFileReader GetReader(string fileName) {
return InsertMockHere;
}
public int GetInternalInfo() {
return someProtectedInfo;
}}
22. Cas 2 – La classe iceberg
• Reconnaissable facilement
– Grosse
• Milliers, dizaines de milliers de lignes
– Peu de méthodes publiques
• Comptées sur une seule main
• C’est quoi le problème?
– Grande complexité cyclomatique
– Interdépendances entre les méthodes
– Donc difficile à tester
23. Cas 2– La classe iceberg – Comment?
• Identifier les différentes responsabilités
– Noms de méthodes
– #Region ou zones de commentaires
• Refactoriser en plusieurs classes
• Si impossible :
– Identifiez les méthodes qui seraient publiques avec un
refactoring
– Changer la visibilité et testez ces méthodes
24. Cas 2– La classe iceberg – Exemple (classe
simplifiée!)
25. Cas 3 – Méthode statique
• Une méthode statique, l’équivalent d’un
singleton
• Une seule version possible pour tout le système
– Donc pas mockable
• Pas un problème pour la tester
• Mais un véritable problème pour tester les
méthodes qui l’appellent
26. Cas 3 – Méthode statique – Comment?
• Ajout d’une interface à la classe
• Ajout de méthodes d’instance
correspondant aux méthodes statiques
– DRY : la méthode d’instance peut
appeler la méthode statique!
– Les appelants ne sont pas affectés
• Éventuellement on pourra enlever
complètement les méthodes statiques
27. Cas 3 – Méthode statique – Code
public class ClassUnderTest
{
public void MethodUnderTest()
{
// ...
var number = AnotherClass.GetNumber();
if (number > 0)
{ }
else
{ }
// ...
}
}
public class AnotherClass
{
public static int GetNumber()
{
return 4;
}
}
28. Cas 3 – Méthode statique – Code
public class ClassUnderTest {
private IAnotherClass _anotherClass;
public ClassUnderTest() { _anotherClass = new AnotherClass(); }
public ClassUnderTest(IAnotherClass anotherClass) { _anotherClass = anotherClass; }
public void MethodUnderTest() {
// ...
var number = _anotherClass.GetNumber();
if (number > 0)
{ }
else
{ }
// ...
}
}
public class AnotherClass: IAnotherClass {
public static int GetNumber() { return 4; }
int IAnotherClass.GetNumber() {
return AnotherClass.GetNumber();
}
}
29. Mais en avez-vous vraiment besoin ?
• Parfois, l’ajout de tests unitaires
demande beaucoup d’efforts
pour peu de gains
• Évaluer d’autres opportunités
– CodedUI / Selenium
– Tests intégrés, mocker
uniquement les données
– Tests comparatifs
• Pas toujours les meilleures
pratiques, mais faut s’adapter au
contexte!
30. Comment le BDD m'a aidé dans mon débogage
• Partir d’un cas d’utilisation (déjà
implémenté) et le descendre en étape BDD
du début (contrôleur UI), passer par le
service et aboutir dans une BD en mémoire
• C’est long
• On vérifie toutes les dépendances et on
traverse toutes les couches
• C’est long, mais après coup notre
compréhension du code s’est vraiment
améliorée
• En plus, le test BDD reste et peut être
rejoué !
31. État d’esprit pour déboguer de Legacy Code
• Voir le code comme une scène de
crime
• Faire un profil de l’agresseur (ou de
ce qui cause le problème)
• Analyser les hot-spots
• Calculer la complexité
• Faire la dissection de l’architecture
• Cartographier une carte de
connaissances de votre système
• Impact sur l’existant
34. C’est impossible d’en faire chez nous car…
• Nous autres, c'est différent
• On a un système de mission
critique à l'entreprise
• Nos utilisateurs ont le contrôle et
le budget
• Notre système est très complexe
• On a des données nominatives
• …
35. C’est notre devoir d’être professionnel
Du code propre n’apparaît
pas de lui-même:
• Vous avez à le produire
• Vous avez à le maintenir
• Vous en faites un
engagement professionnel
36. Combattre le résistance en discutant
• La plupart des gestionnaires
défendent leurs échéanciers et les
requis avec passion.
– Cela fait partie de leurs responsabilités.
• Votre responsabilité :
– Défendre également le code avec
passion.
• Ayez le courage de dire la vérité et
de travailler pour arriver à un terrain
d’entente.
• Discuter avec votre PO ou votre chef
d’équipe. Il devrait être sensible à la
qualité produite. Donc de supporter
l’équipe quand il sent qu’un
refactoring est nécessaire.
37. Il faut alors être stratégique
• Cela ne donne pas grand chose de
travailler sur du code qui va bien et qui
n’a pas de problème régulièrement.
Don’t fix it, if it’s not broken.
• Trouver les points chauds de votre
système:
– Bogues réguliers
– Difficiles à travailler
– Modification à faire prochainement
– Logique d’affaires importante
• Progresser par la technique des
petits pas.
38. Bien évaluer
Valeur de cet investissement :
• Potentiel
• Bloquant actuel
• Criticité du système
• Nombre de personnes impactées
• Durée de vie du système
• Refonte prévue?
39. La résistance peut donc être combattue
• C’est souvent le premier pas le
plus difficile.
• Ne pas oublier :
– Être stratégique
– Évaluer le tout
• Y aller un test à la fois:
– Et son Refactoring de code
qui le suit.
40. En fait, on en revient à la transparence
Le premier pilier de l’agilité!
42. Comprendre le code
• Les tests nous apportent une façon de
comprendre le code.
• D’une certaine manière, les tests nous
font travailler dur pour arriver à cette
compréhension.
• Il est facile de faire des tests en
présence d’une bonne conception.
43. Ne pas hésiter à utiliser des outils modernes
• Legacy != outils désuets
• Exemples :
– Intégration continue
– Dernière version de l’IDE
– Dernières versions des
librairies et frameworks
– Système Legacy dans Docker
44. Ne pas oublier les bases
• OOP
• Clean Code
• Maîtrisez et appliquez les
principes SOLID
• Boy Scout Rule
• Agile Modeling (Scott Ambler)
45. Se prémunir contre le Legacy Code à la base
• Les développeurs doivent
demander Quoi, Pourquoi et
pour Qui avant de trouver le
Comment.
• Ce n’est pas aux analystes et
utilisateurs de leur dire le
Comment.
• On veut savoir du client/PO
qu’est-ce qu’ils veulent,
pourquoi ils le veulent et pour
qui cela va être utile.
46. Faire la conception en équipe
• Les meilleures conceptions sont
effectuées en équipe, et non par
une seule personne.
• La compréhension de
l’architecture ainsi que son
adoption est répandue dans toute
l’équipe.
• Pourquoi s’en passer ?
47. Ou du Mob Programming !
• Approche où l’équipe
complète (client, analyste
et PO aussi) travaille sur la
même chose :
– Au même endroit
– En même temps
– Sur le même ordinateur
• Un peu similaire au pair
programming
48. En résumé, nous avons vu…
• C’est quoi du Legacy Code
• Comment faire pour le tester
• Comment affronter la résistance
• Comment s’en prémunir au jour le jour
49. Alors, tester du du Legacy Code,
• Met à l’épreuve et renforce nos
habilités en:
– Programmation Orientée-Objet
– Conception & Architecture
• Offre un challenge qui peut donc
être instructif et amusant !
• Nous rend plus professionnel en bout
de ligne.