(Slides de la présentation à la conférence Agile France 2010)
Vous avez lu la cheatsheet de JMock, la documentation d’EasyMock, la FAQ de Mockito et pourtant, la moitié de votre code n’est toujours pas couvert. Vous n’arrivez juste pas à poser de tests dessus.
Votre code est intestable.
L’objectif de la session est de montrer pourquoi certains codes ne peuvent pas être testés et ce qui peut être fait pour y remédier. Nous verrons ainsi pourquoi il vaut mieux respecter la loi de Demeter et faire de l’injection de dépendances. Nous aborderons également les problèmes des classes avec trop de responsabilités et des états globaux.
111. Symptômes d’un code intestable Isolabilité Simplicité Classes hyperactives Méthodes chargées Interroger des collaborateurs Etats globaux Annuaires Blocs statiques Instanciation directe Constructeur cher Mélanger service et valeur Héritage
112. Vers du code testable Isolabilité Simplicité Passer les objets utilisés Directement en paramètre Pas de longues initialisations Injecter les dépendances Injecter les dépendances Injecter les dépendances Injecter les dépendances Donner des veines pour les mocks Limiter dépendances directes Supprimer les singletons, static et annuaires Petites classes 1 scénario = 1 test Séparer les responsabilités Composition plutôt Qu’héritage 1 classe = 1 responsabilité Petites méthodes Polymorphisme
113.
114.
Notas do Editor
On va commencer par un petit sondage En fait je veux dire unitaire Qui fait systématiquement des tests? Qui essaie toujours? Quels problemes? Des tests qui marchent tout seul et pas en groupe, au moment de la release. Des NPE à ajouter partout;
http://img.turbo.fr/02665474-photo-pieces-detachees-accessoires.jpg .. un pb et plusieurs causes possibles .. Unité avec branchement pour permettre tester isolément .. Et des cas d’erreurs (introduction de fakes) voyants qui doivent apparaitre
Qu’est ce qu’on y gagne? Satisfaction : on sait que ça marche Qd pb, on peut innocenter le code Specs Rassure, confiance pour remanier le code Il faut des branchements
On ne veut pas d’effets de bord. Savoir d’où vient un pb. … donner des veines / des branchements l’intérieur d’un PC … exemple d’une boite noir : - un truc soudé - une freebox
Given : carburant + huile moteur + batterie ok When : tourne la clef Then : démarrage du moteur
Ok en théorie mais concrètement, comment reconnaître le loup? Savoir, mais c pas si evident de le reconnaître. L’idée c’est aussi de savoir reconnaître les signes d’un code intestable. Ce qui fait qu’on sait qu’un loup et un loup, meme s’il est déguisé en mere grand.
Pour le premier symptome
IO + boucle Lenteur à trimballer pour tous les cas de test
Graphe d’objets / logique métier Dico : obtenir les définitions Factory : construire la référence des définitions
Développer des logiciels Installer des logiciels et du matériel Fournir du matériel
Développer des logiciels Installer des logiciels et du matériel Fournir du matériel
Mélane logique métier + création du graphe d’objets
Du coup quand on voit que DANS demarrer, on construit des objets, qq chose cloche
Objet valeur ne coute pas à etre instancier.
Mais dépendant du framework Un peu bourrin : toutes les instances sont mockées Peu répandu
Le meme genre de probleme
Impossible de simuler un autre comportement Impossible de changer son état une fois que c’est initialisé => Certains tests peuvent marcher
Le meme genre de probleme
L’héritage est une bonne idée mais il est souvent mal utilisé Un lion court comment? … à quatre pattes Souris aussi Donc j’en hérite – un peu extrême car ce n’est pas un « est »
Toutes les sous classes sont fortement couplées aux implémentations de leur classe mère Des fois ça ne se justifie pas Des fois oui, mais ca rend les choses trop compliquées à tester. Est-ce que ça vaut le coup? seDeplacer manger
- fragile aux modifications de la classe mere - potentiellement lent - pas la main sur le comportement hérité : on peut pas lui en simuler un autre
- 5e symptome qui empeche le code d'etre isolable
Les etats globaux sont un véritable fléau, alors pour eux, on va prendre un exemple un peu plus long.
- tiers de la prez, c'est l'heure de prendre un café - "je vous propose de prendre un café"
BDD pas completement initialisé
Robinet est fermé par défaut.
Ouf… mais il a quand meme fallu tatonné pour pouvoir tout initialisé. Autant de dépendances cachées.
Débogage difficile Si oubli de réinitialiser l’état à la fin Pas de tests en parallèle Chaque test doit commencer avec un état présumé pour fonctionner. Mais cela peut avoir été modifié par un autre test. Impossible de lancer les tests en parallèle => + long Débogage difficile
Un seul singleton = 100 valeurs partagées
Mais modification du code de production pour le test… très intrusif. Violation de l’encapsulation. Et plus tard on croit que c’est pour la prod => jamais nettoyé. Meme si on spécifie que c’est pour les tests, on peut oublier de resetter l’état à la fin et perturber un autre test. Non unitaire Très difficile à déboguer Les tests s’impactent les uns les autres
Static = classes manquantes. Identifier les différentes responsabilités et en extraire des classes.
Le meme genre de probleme
nous ne savons pas ce qu'il faut réellement mocker avant d'avoir lancé le code, bien qu'on puisse le soupçonner en regardant les attributs de la classe ; il est difficile de connaître les dépendances cachées de la classe ; si l'on ajoute une dépendance à la classe, cela ne se verra pas clairement à cause du Locator. Le code compilera toujours, certains tests passeront, d'autres pas ; pour avoir une House, nous avons besoin de manipuler un Locator dans le test. Cependant, ce dernier a aussi connaissance d'autres services, qu'il faut aussi créer. De fil en aiguille, c'est une application entière qu'il faut construire pour les tests... il faut rendre le Locator compatible avec nos tests en créant une interface spécifique, en le surchargeant, en le mockant ou en rajoutant des setters pour pouvoir y injecter nos dépendances ; ils mélangent la construction des objets avec les lookup d'objets.
- on aborde maintenant des symptomes, qui sans etre intestables sont genantes.
Le meme genre de probleme
Est-ce qu’il vaut mieux acheter au producteur ou à un intermédiaire? Pourquoi? Plus direct Moins cher
Violation du SRP (collaborateur qui sert de service locator) Meme mensonge
Violation du SRP (collaborateur qui sert de service locator) Pourquoi c’est mal Tromperie : on a besoin « Utilisateur et Commande » Il faut regarder dans le code Débogage complexifié : D’où vient telle exception? Gâchis et moins de lisibilité En se trainant cet objet qu’on n’utilise pas Couplage fort avec l’objet intermédiaire Difficile à enlever plus tard Une modification peut avoir beaucoup d’impacts Du côté des tests : Laborieux : on passe le context et corrige les NPE découvert au fur et à mesure Phase d’initialisation du test complexifiée Test fortement couplé à l’implémentation avec l’intermédiaire
Fausse bonne idée car Induit en erreur car on croit réellement qu’on a juste besoin de « context ». Pour écrire le test, il faut corriger les NPE au fur et à mesure, à l’aveugle. Pas si flexible qu’on croit car on peut pas refactorer facilement. On ne sait pas quels sont les collab nécessaires juste en regardant l’API. On ne comprend pas les enjeux du code facilement du coup.
Fausse bonne idée car Induit en erreur car on croit réellement qu’on a juste besoin de « context ». Pour écrire le test, il faut corriger les NPE au fur et à mesure, à l’aveugle. Pas si flexible qu’on croit car on peut pas refactorer facilement. On ne sait pas quels sont les collab nécessaires juste en regardant l’API. On ne comprend pas les enjeux du code facilement du coup.
Le meme genre de probleme
- lisibilité de la classe (ca revient à avoir un grand main et tout dedans, une appli en C) - enlever "test plus complexe" et remonter "lisibilité" et "maintenabilité" Pour chaque modif, ce sera elle à modifier Debogage : si beaucoup d
Très très fréquent
Méthode statique : méthode à qui il manque une classe
Méthode statique : méthode à qui il manque une classe
Méthode statique : méthode à qui il manque une classe
- si condition 1 rempli, si 1 et 2, si 2 et 3, si 1 et 3 => false. Tests peu robuste aux changements.
S’il y a plusieurs chemins d’exécutions possibles et que c’est confus Design pattern stratégies Vérification d’objets NULL
Design pattern stratégies Surtout si if redondants
Pour chaque des conditions, on verifie que facturer et appeler et que true est retourné
Le meme genre de probleme
GenererFacture Pour vérifier qu’un service marche
Ont des données Font des choses
Savoir, mais c pas si evident de le reconnaître. L’idée c’est aussi de savoir reconnaître les signes d’un code intestable. Ce qui fait qu’on sait qu’un loup et un loup, meme s’il est déguisé en mere grand.
Exceptions Si l’objet global est une constante / immutable Et ses attributs transitifs aussi Ok si primitif, attention si objet (ça peut changer) Si l’information ne va que dans un sens Logger