Your SlideShare is downloading. ×
Comment écrire du code testable ?
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Comment écrire du code testable ?

4,525
views

Published on

(Slides de la présentation à la conférence Agile France 2010) …

(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.

Published in: Technology, Education

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
4,525
On Slideshare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
76
Comments
0
Likes
1
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide
  • 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://www.internetkids.fr/public/plus-belle-plage.jpg
  • http://www.thau-agglo.fr/IMG/jpg/13_Une_partie_de_Beach_Volley_x700_.jpg
  • http://bichett01.skyrock.com/photo.html?id_article=1242496956&rev=0
  • 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
  • Pour les méthodes et classes longues
  • Transcript

    • 1. Comment écrire du code testable Conférence Agile France 2010 Florence CHABANOIS
    • 2.  
    • 3.  
    • 4. Frein ? Direction assistée? Boite de vitesse ? Pneus défectueux ?
    • 5.  
    • 6. Tests unitaires
      • Tester une partie du produit
      • Simuler un comportement différent de la production
        • EasyMock, Mockito, JMock
      • Pour pouvoir tester unitairement :
        • Les composants doivent être séparables
    • 7. Isolation
      • Pour permettre la séparation
        • Externaliser les dépendances
          • public Moteur() {
          • reservoirHuile = new ReservoirHuilePlein();
          • }
          • public MoteurOk(ReservoirHuile reservoirHuile) {
          • this .reservoirHuile = reservoirHuile;
          • }
        • Bannir les dépendances cachées
    • 8. Une solution
      • Le Test Driven Development
            • Given… When… Then
            • Implémentation
            • Refactoring
      • Mais..
        • 1. Il y a souvent du code déjà existant
          • … sur lequel il faut poser des tests
          • … dont le nouveau code dépend
    • 9. Le TDD ne donne pas l’immunité
        • 2. Le code peut être testé et
          • Classes et méthodes fourre-tout
          • Les tests souffrent
            • Performances (constructeur couteux)
            • Compréhensibilité (tests = spécifications)
          • Le développeur aussi
            • Tests lents
            • Maintenabilité
    • 10. Leitmotiv
      • Deux lignes de conduite
      Isolabilité Simplicité
    • 11.  
    • 12. 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
    • 13. Ennemis jurés
    • 14.  
    • 15.
      • public class Dictionnaire {
      • private Map<String, String> definitions = new HashMap<String, String>();
      • public Dictionnaire() throws IOException {
      • File file = new File(&quot;francais.txt&quot;);
      • BufferedReader reader = new BufferedReader( new FileReader(file));
      • String ligne;
      • while ((ligne = reader.readLine()) != null ) {
      • String[] tableauLigne = ligne.split(&quot;:&quot;);
      • definitions.put(tableauLigne[0], tableauLigne[1]);
      • }
      • }
      • public String getDefinition(String mot) {
      • return definitions.get(mot);
      • }
    • 16.
      • Test (n.m.)
          • Opération destinée à contrôler le bon fonctionnement d'un appareil ou la bonne exécution d'un programme dans son ensemble.
    • 17. Tester getDefinition()
      • @Test
      • public void testGetDefinition() throws IOException {
      • Dictionnaire dico = new Dictionnaire();
      • String returnedDefinition = dico.getDefinition(&quot;test&quot;);
      • assertThat (returnedDefinition, is ( equalTo (&quot;Opération destinée à etc.&quot;)));
      • }
    • 18.
      • public Dictionnaire() throws IOException {
      • File file = new File(&quot;francais.txt&quot;);
      • BufferedReader reader = new BufferedReader( new FileReader(file));
      • String ligne;
      • while ((ligne = reader.readLine()) != null ) {
      • String[] tableauLigne = ligne.split(&quot;:&quot;);
      • definitions.put(tableauLigne[0], tableauLigne[1]);
      • }
      • }
      Test très lent Obligé d’avoir un fichier
    • 19.
      • @Test
      • public void testGetDefinition_WhenMotNonTrouve() throws IOException {
      • Dictionnaire dico = new Dictionnaire();
      • (…)
      • }
      • @Test
      • public void testGetDefinition_WhenMotNonValide() throws IOException {
      • Dictionnaire dico = new Dictionnaire();
      • (…)
      • }
    • 20. Symptômes d’un code intestable
      • Un constructeur cher
    • 21. Un constructeur trop cher
      • Pourquoi c’est mal
        • On ne peut PAS éviter d’instancier une classe pour la tester
        • Enlève une veine ( seam )
          • Test pas isolé
          • Test potentiellement couteux
          • Difficile de simuler un autre comportement
        • Voire plus, si c’est utilisé par d’autres tests
    • 22. Un constructeur trop cher
      • Signes d’alertes
        • If, switch, loop
        • new d’objets
        • Des appels statiques
        • … En fait autre chose que des assignations d’attributs
    • 23. Un constructeur trop cher
      • Comment y remédier
        • Méthode init() à appeler après le constructeur
          • Pas de test dessus
        • Un constructeur spécial pour le test
          • Déplacement du problème
        • Extraire dans une autre méthode, qu’on surcharge
    • 24. Code : Extraction de la méthode
      • public DictionnairePatche() throws IOException {
      • initialize();
      • }
      • protected void initialize() throws FileNotFoundException, IOException
      • {
      • File file = new File(&quot;francais.txt&quot;);
      • BufferedReader reader = new BufferedReader( new FileReader(file));
      • String ligne;
      • while ((ligne = reader.readLine()) != null ) {
      • String[] tableauLigne = ligne.split(&quot;:&quot;);
      • definitions.put(tableauLigne[0], tableauLigne[1]);
      • }
      • }
    • 25. Test : instanciation d’une sous classe
      • static class DictionnairePatchForTest extends DictionnairePatche {
      • @Override
      • protected void initialize () throws FileNotFoundException, IOException {
      • // nothing
      • }
      • }
      • @Test
      • public void testGetDefinition() throws IOException {
      • Dictionnaire dico = new DictionnairePatchForTest();
      • String returnedDefinition = dico.getDefinition(&quot;test&quot;);
      • assertThat (returnedDefinition, is ( equalTo (&quot;Opération destinée à contrôler le bon fonctionnement d'un appareil ou la bonne exécution d'un programme dans son ensemble.&quot;)));
      • }
    • 26. Un constructeur trop cher
      • Comment y remédier
        • Méthode init() à appeler après le constructeur
          • Pas de test dessus
        • Un constructeur spécial pour le test
          • Déplacement du problème
        • Extraire dans une autre méthode, qu’on surcharge
          • Pas de test dessus
    • 27. Un constructeur trop cher
      • Signes d’alertes mis à jour
        • If, switch, loop
        • new d’objets
        • Des appels static
        • … . En fait autre chose que des assignations d’attributs
        • Un constructeur spécial test
        • Init()
        • Du code spécial test : @VisibleForTesting
    • 28. Un constructeur trop cher
      • Comment y remédier
        • Méthode init()
        • Un constructeur spécial pour le test
        • Extraire dans une autre méthode, qu’on surcharge
      • Comment y remédier mieux
        • Faire des constructeurs relais uniquement
        • Passer les collaborateurs prêts en paramètres au lieu de les créer
          • Injection de dépendances
          • Factories
    • 29.
      • public class DictionnaireTestable {
      • private Map<String, String> definitions = new HashMap<String, String>();
      • public DictionnaireTestable( Map<String, String> definitions ) throws IOException {
      • this .definitions = definitions;
      • }
      • }
      Le constructeur ne coute plus cher Veine créée : Le code n’est plus « collé »
    • 30.
      • public class DictionnaireFactory {
      • public static Dictionnaire buildFromTextFile() throws IOException {
      • Map<String, String> definitions = new HashMap<String, String>();
      • File file = new File(&quot;francais.txt&quot;);
      • BufferedReader reader = new BufferedReader( new FileReader(file));
      • String ligne;
      • while ((ligne = reader.readLine()) != null ) {
      • String[] tableauLigne = ligne.split(&quot;:&quot;);
      • definitions.put(tableauLigne[0], tableauLigne[1]);
      • }
      • return new DictionnaireTestable(definitions);
      • }
      • }
      Séparation des responsabilités
    • 31. Principe de responsabilité unique
    • 32. Principe de responsabilité unique
      • Je cherche sur Internet de quelle matière première j’ai besoin pour en fabriquer
      • J’appelle Air France pour réserver un billet d’avion et aller en chercher en Chine
      • Je demande au service Bureautique de m’en installer un nouveau
    • 33. Principe de responsabilité unique
      • Et le service Bureautique ?
        • Cherche sur Internet de quelle matière première il a besoin pour en fabriquer
        • Appelle Air France pour réserver un billet d’avion et aller en chercher en Chine
        • Le commande chez son fournisseur
    • 34. Principe de responsabilité unique Etudes Fournisseur Bureautique
    • 35. Principe de responsabilité unique
      • Créer le graphe d’objets est une responsabilité à part entière
        • public class Moteur {
        • private ReservoirHuile reservoirHuile ;
        • public Moteur() {
        • reservoirHuile = new ReservoirHuilePlein();
        • }
        • public void demarrer () {
        • // (...)
        • }
        • public void signalerManqueHuile () {
        • // (...)
        • }
        • }
      Création du graphe d’objets Logique métier
    • 36. Focus sur demarrer()
      • public void demarrer() {
      • Moteur moteur = new Moteur();
      • moteur.demarrer();
      • BoiteDeVitesse boiteVitesse = new BoiteDeVitesse();
      • boiteVitesse.passerLaPremiere();
      • Embrayage embrayage = new Embrayage();
      • embrayage.relacher();
      • Accelerateur accelerateur = new Accelerateur();
      • accelerateur.appuyer();
      • }
    • 37. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
    • 38. Des instanciations directes
      • Pourquoi c’est mal
        • Couplage fort
        • Enlève une veine ( seam )
          • Test pas isolé
          • Test potentiellement couteux
          • Difficile de simuler un autre comportement
    • 39. Des instanciations directes
      • Signes d’alertes
        • Des « new » dans une classe autre que Factory ou Builder
    • 40. Des instanciations directes
      • Comment y remédier
        • Framework de mocks : JMockit, Powermock
      • Comment y remédier mieux
        • Passer les objets nécessaires en paramètres de la méthode
        • Séparer construction du graphe d’objets de la logique métier
          • Injection de dépendances
          • Factories
    • 41. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
    • 42. Des blocs statiques
        • public class Joueur {
        • private static Plateau plateau ;
        • static {
        • if (Environnement. IS_DEMO ) {
        • plateau = new PlateauCommercial();
        • } else {
        • plateau = new PlateauDeDemo();
        • }
        • }
        • public void joindre(Partie partie) {
        • }
        • }
    • 43. Des blocs statiques
      • Pourquoi c’est mal
        • Couplage très fort
          • Pas possible de le remplacer par un mock
          • Ni de le surcharger dans les tests
          • Potentiellement très couteux
        • Effets de bord entre des tests censés être isolés
        • Le test passe, parfois
        • Etat permanent
    • 44. Des blocs statiques
      • Signes d’alertes
        • Static {}
        • Un test qui ne fonctionne plus au sein d’une suite
      • Comment y remédier
        • Supprimer tous les bloc statiques et introduire des classes
        • Passer les collaborateurs en paramètres au lieu de les créer
          • Injection de dépendances
          • Factories
    • 45.
      • public class JoueurTestable {
      • private Plateau plateau;
      • public JoueurTestable( Plateau plateau ) {
      • this .plateau = plateau;
      • }
      • public void joindre(Partie partie) {
      • (…)
      • }
      • }
    • 46. Spring-jeu.xml
      • <bean class= &quot;fr.soat.agileconference2010.blocstatic.JoueurTestable&quot; id= &quot;joueur1&quot; scope= &quot;prototype&quot; >
      • <constructor-arg ref= &quot;plateau&quot; ></constructor-arg>
      • </bean>
      • <bean class= &quot;fr.soat.agileconference2010.blocstatic.metier. PlateauCommercial &quot; id= &quot;plateau&quot; scope= &quot; singleton &quot; ></bean>
    • 47. testJoindre()
      • Plateau plateau = new PlateauDeDemo ();
      • JoueurTestable joueur = new JoueurTestable(plateau);
      • joueur.joindre( new Partie());
      • //Verifications
    • 48. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
    • 49. Des dynasties de classes ?
    • 50.  
    • 51. Des dynasties de classes
      • Pourquoi c’est mal
        • Couplage fort avec classe mère
        • Lenteur
        • Fragilité
        • Tests plus difficiles à maintenir (redondance)
    • 52. Des dynasties de classes
      • Signes d’alertes
        • Quand le code devient difficile à tester
        • Quand les tests sont redondants / difficile à maintenir à cause de la classe mère
      • Comment y remédier
        • Utiliser la composition pour réutiliser du code
        • Limiter l’héritage aux besoins de polymorphisme
    • 53.  
    • 54.  
    • 55. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
      • Des états globaux
    • 56.
      • a = new X().traiter();
      • b = new X().traiter();
      a = b ?
    • 57.
      • Source : http://misko.hevery.com/2009/10/07/design-for-testability-talk/
    • 58.  
    • 59. On veut poser un test sur l’expresso
      • public class MachineACafe {
        • public void payer( float montant){
        • (…)
        • }
        • public Expresso preparerExpresso() {
        • (…)
        • }
        • public void brancher(){
        • (…)
        • }
      • }
    • 60. Test de l’expresso
      • @Test
      • public void testPreparerExpresso() {
      • MachineACafe machineACafe = new MachineACafe();
      • Expresso expresso = machineACafe.preparerExpresso();
      • assertThat (expresso.estConforme(), is ( true ));
      • }
      Null Pointer Exception
    • 61. Test de l’expresso
      • @Test
      • public void testPreparerExpressoEssai2() {
      • MachineACafe machineACafe = new MachineACafe();
      • machineACafe.setBaseDeDonnees( new BaseDeDonnees());
      • Expresso expresso = machineACafe.preparerExpresso();
      • assertThat (expresso.estConforme(), is ( true ));
      • }
      Null Pointer Exception
    • 62. Test de l’expresso
      • @Test
      • public void testPreparerExpressoEssai3() {
      • MachineACafe machineACafe = new MachineACafe();
      • BaseDeDonnees baseDeDonnees = new BaseDeDonnees();
      • baseDeDonnees.init(&quot;myUrl&quot;, &quot;myLogin&quot;, &quot;myPassword&quot;);
      • machineACafe.setBaseDeDonnees(baseDeDonnees);
      • Expresso expresso = machineACafe.preparerExpresso();
      • assertThat (expresso.estConforme(), is ( true ));
      • }
      Null Pointer Exception
    • 63. Test de l’expresso
      • @Test
      • public void testPreparerExpressoEssai4() {
      • MachineACafe machineACafe = new MachineACafe();
      • BaseDeDonnees baseDeDonnees = new BaseDeDonnees();
      • baseDeDonnees.init(&quot;myUrl&quot;, &quot;myLogin&quot;, &quot;myPassword&quot;);
      • baseDeDonnees.setNotificateur( new Notificateur());
      • machineACafe.setBaseDeDonnees(baseDeDonnees);
      • Expresso expresso = machineACafe.preparerExpresso();
      • assertThat (expresso.estConforme(), is ( true ));
      • }
      CafeException
    • 64. Pourquoi CafeException ?
      • public void verifierPreconditions() {
      • if (! robinetActive ()) {
      • final String erreur = &quot;Vérifier le robinet&quot;;
      • baseDeDonnees.logguerErreur( this , erreur);
      • throw new CafeException(erreur);
      • }
      Hein, quel robinet ?
    • 65. Pourquoi CafeException ?
      • private boolean robinetActive() {
      • Robinet robinet = Robinet. getInstance ();
      • return ( robinet.estOuvert () && robinet.estConnecte( this ));
      • }
    • 66. Test de l’expresso
      • @Test
      • public void testPreparerExpressoEssai5() {
      • MachineACafe machineACafe = new MachineACafe();
      • final BaseDeDonnees baseDeDonnees = new BaseDeDonnees();
      • baseDeDonnees.init(&quot;myUrl&quot;, &quot;myLogin&quot;, &quot;myPassword&quot;);
      • baseDeDonnees.setNotificateur( new Notificateur());
      • machineACafe.setBaseDeDonnees(baseDeDonnees);
      • Robinet. getInstance ().ouvrir();
      • Expresso expresso = machineACafe.preparerExpresso();
      • assertThat (expresso.estConforme(), is ( true ));
      • }
      Ok ! Ok !
    • 67. Je dev Je dev Je dev Je dev Je dev Je dev Je dev Je dev Mon code Il dev Il dev Il dev COMMIT Il dev Il dev Il dev Il dev Il dev COMMIT Son code
    • 68. Boom
      • public void testPreparerVerreEau_whenDefaultValues() {
      • FontaineAEau fontaine = new FontaineAEau();
      • VerreEau verre = fontaine.preparerVerreEau();
      • assertThat (verre, is ( nullValue ()));
      • }}
      AssertionError : expected NULL « Son code »
    • 69. Test de l’expresso
      • @Test
      • public void testPreparerExpressoEssai6() {
      • MachineACafe machineACafe = new MachineACafe();
      • final BaseDeDonnees baseDeDonnees = new BaseDeDonnees();
      • baseDeDonnees.init(&quot;myUrl&quot;, &quot;myLogin&quot;, &quot;myPassword&quot;);
      • baseDeDonnees.setNotificateur( new Notificateur());
      • machineACafe.setBaseDeDonnees(baseDeDonnees);
      • Robinet. getInstance ().ouvrir();
      • Expresso expresso = machineACafe.preparerExpresso();
      • assertThat (expresso.estConforme(), is ( true ));
      • Robinet. getInstance ().fermer();
      • }
      Ok pour le moment…
    • 70. Des états globaux
      • Pourquoi c’est mal
        • Mensonge : « il n’y a pas de dépendances. »
          • Méthode statique ou Singleton = dépendance cachée.
        • Pas de veine pour placer un mock
          • Test pas isolé
          • Test potentiellement couteux
          • Difficile de simuler un autre comportement
        • Risque de perturbations avec d’autres tests
          • Etat présumé
          • Plus longs à lancer
          • Débogage difficile
    • 71. Des états globaux
      • Signes d’alertes
        • Des singletons
        • Du code static : variable, bloc, méthode
        • … même un seul !!!
          • « chargement global (global load) » : nombre de variables pouvant être modifiées par un état global
        • Des tests qui fonctionnent seuls mais pas en groupe
          • ou vice versa
    • 72. Des états globaux
      • Comment y remédier
        • Suppression du final et introduction de setters
        • Isoler le problème dans une autre méthode, qu’on surcharge.
      Violation de l’encapsulation Code brouillé Et peu nettoyable Oubli de reset Ordre compte Lisibilité
    • 73. Des états globaux
      • Signes d’alertes mis à jour
        • Des singletons
        • Du code static : variable, bloc, méthode
        • … même un seul !!!
          • car « chargement global / global load » : le nombre de variables qui peuvent être modifiées par un état global
        • Des tests qui fonctionnent seuls mais pas en groupe
          • ou vice versa
        • Du code spécial test
          • Des setters, reset, init dans les singletons
          • @VisibleForTesting
    • 74. Des états globaux
      • Comment y remédier réellement
        • Bannir singleton et code static
        • Décliner en classes
        • Injection de dépendances
    • 75. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
      • Des états globaux
      • Annuaire de service
    • 76. Annuaire de services
      • public Maison(Locator locator) {
      • porte = locator.getPorte();
      • fenetre = locator.getFenetre();
      • toit = locator.getToit();
      • }
    • 77. Annuaire de services
      • Pourquoi c’est mal
        • Tromperie
          • « il n’y a pas de dépendances »
          • « il n’y en a qu’une seule »
        • Application entière à initialiser
    • 78. Annuaire de services
      • Signes d’alertes
        • « Registry », « context », « locator »
      • Comment y remédier
        • Passer les objets réellement utilisés
        • Injection de dépendances
    • 79. Pollueurs
    • 80. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
      • Des états globaux
      • Annuaire de service
      • Interroger des collaborateurs
    • 81.  
    • 82. Avoir des intermédiaires
      • public void facturer(Commande commande, Client client) {
      • banqueService.prelever(client.getCompteBancaire(), commande.getTotal() );
      • emailService.notifierPrelevement(client.getEmail());
      • }
    • 83. Avoir des intermédiaires
      • Pourquoi c’est mal
        • Tromperie : « on a besoin de Commande et Client »
        • Couplage fort avec l’objet intermédiaire
        • Lisibilité
        • Débogage plus complexe (exception)
        • Initialisation du test plus complexe
    • 84. Avoir des intermédiaires
      • Signes d’alertes
        • « context », « environment », « container »
        • Objets passés mais jamais utilisés directement
        • Plus d’un point
          • env.getUser().autoconnect();
        • Dans les tests :
          • Des mocks qui retournent des mocks
          • Devoir mocker des getters/setters
    • 85. Avoir des intermédiaires
      • Comment y remédier
        • Appliquer le principe de connaissance minimale (Loi de Demeter)
          • toute méthode M d'un objet O peut uniquement invoquer les méthodes de
            • lui-même
            • ses attributs
            • ses paramètres
            • les objets qu'il crée/instancie
        • Passer directement les objets réellement utilisés
    • 86.
      • public void facturer(CompteBancaire compte , double montant , String email ) {
      • banqueService.prelever(compte, montant);
      • emailService.notifierPrelevement(email);
      • }
    • 87. Initialisation du test avant
      • Client client = new Client();
      • final CompteBancaire compte = new CompteBancaire();
      • client.setCompteBancaire(compte);
      • final String email = &quot;toto@email.fr&quot;;
      • client.setEmail(email);
      • Commande commande = new Commande();
      • final double total = 20.0;
      • commande.setTotal(total);
      • // When
      • manager.facturer(commande, client);
    • 88. Initialisation du test après
      • final CompteBancaire compte = new CompteBancaire();
      • final String email = &quot;toto@email.fr&quot;;
      • final double montant = 20.0;
      • // When
      • manager.facturer(compte, montant, email);
    • 89. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
      • Des états globaux
      • Annuaire de service
      • Interroger des collaborateurs
      • Des classes hyperactives
    • 90. CommandeManager
    • 91. Des classes hyperactives
      • Pourquoi c’est mal
        • Classe fourre-tout
        • Peu robuste aux changements
        • Lisibilité
        • Maintenabilité
    • 92. Des classes hyperactives
      • Signes d’alertes
        • « manager », « utils », « helper »
        • Qu’est ce qu’elle fait? Et
        • Pas évidente à comprendre pour un nouvel arrivant / Pas facile d’avoir en tête ce qu’elle fait en une fois
        • Difficile de trouver un nom à la classe
        • Quand un champ n’est utilisé que par quelques méthodes
        • Beaucoup de champs et/ou collaborateurs
        • Beaucoup de méthodes
        • Méthodes avec peu de rapport les unes les autres
        • Méthodes statiques
    • 93. Des classes hyperactives
      • Comment y remédier
        • Etapes
          • Identifier les responsabilités de la classe
          • Les nommer
          • Les extraire dans autant de classes
          • Une classe peut avoir le rôle d’orchestrer
        • Comment identifier les responsabilités?
          • Repérer les méthodes qui ne sont utilisées que par un ou quelques champs
          • Repérer les méthodes statiques et les rendre à leur paramètres (ou wrapper de paramètres)
            • listerCommandes(Client client)
          • Regrouper méthodes qui se ressemblent
          • Regrouper les attributs souvent utilisés ensemble
    • 94. Des classes hyperactives
      • Comment y remédier (suite)
        • Si code legacy
          • Extraire une classe pour chaque modification / nouvelle fonctionnalité
    • 95. Des classes hyperactives
      • Comment y remédier (suite)
        • Si code legacy
          • Extraire une classe pour chaque modification / nouvelle fonctionnalité
        • Imbriquer les collaborateurs
      A Y Z X W A Y Z X W
    • 96. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
      • Des états globaux
      • Annuaire de service
      • Interroger des collaborateurs
      • Des classes hyperactives
      • Des méthodes trop chargées
    • 97. Au guichet du Grand Huit
      • public boolean laisserPasser(Personne personne) {
      • if (personne.getAge() > 12 && personne.getTaille() > 1.3
      • && personne.estEnBonneSante()) {
      • if (
      • (personne.getAge() < 18 && personne.estAccompagne())
      • || (personne.getAge() >= 18)){
      • facturer(personne);
      • return true ;
      • }
      • }
      • return false ;
      • }
    • 98. Des méthodes trop chargées
      • Pourquoi c’est mal
        • Augmente la complexité des tests
        • Très sensible aux modifications
        • Difficile de comprendre tout de suite le fonctionnement
    • 99. Des méthodes trop chargées
      • Signes d’alertes
        • Si ça dépasse l’écran
        • S’il y a des ifs, switch, loop….
          • Plus d’un && ou ||
          • If/else imbriqués
          • Check NULL
        • Des commentaires sont nécessaires pour expliquer la logique
        • Une complexité élevée (cf sonar)
    • 100. Des méthodes trop chargées
      • Comment y remédier
        • Découper en plusieurs autres méthodes
        • Extraire d’autres classes et déléguer
        • Favoriser le polymorphisme
        • Retourner des objets vides plutôt que des NULL
        • Donner des valeurs par défaut (pour éviter un else)
    • 101. Au guichet du Grand Huit
      • public boolean laisserPasser(Personne personne) {
      • if (personne.getAge() > 12 && personne.getTaille() > 1.3
      • && personne.estEnBonneSante()) {
      • if (
      • (personne.getAge() < 18 && personne.estAccompagne())
      • || (personne.getAge() >= 18)){
      • facturer(personne);
      • return true ;
      • }
      • }
      • return false ;
      • }
      estPhysiquementCompatibleJeuxIntenses(personne) estLegalementCompatibleJeuxIntenses(personne)
    • 102. Extraction de méthodes
      • private boolean estLegalementCompatibleJeuxIntenses(Personne personne) {
      • return estMineurAccompagne(personne) || estMajeur(personne);
      • }
      • private boolean estPhysiquementCompatibleJeuxIntenses(Personne personne) {
      • return personne.getAge() > 12 && personne.getTaille() > 1.3 && personne.estEnBonneSante();
      • }
      • private boolean estMajeur(Personne personne) {
      • return personne.getAge() >= 18;
      • }
      • private boolean estMineurAccompagne(Personne personne) {
      • return personne.getAge() < 18 && personne.estAccompagne();
      • }
    • 103. Extraction d’une autre classe
      • public class GrandHuitRefactore {
      • private PersonneVerificateur personneChecker;
      • public boolean laisserPasser(Personne personne) {
      • if (personneChecker.physiqueMinimum(personne) && personneChecker.estConsidereMajeur(personne)) {
      • facturer(personne);
      • return true ;
      • }
      • return false ;
      • }
    • 104. Polymorphisme
      • public class Commande {
      • protected static final double TAUX_REDUIT = 0.5;
      • protected static final double TAUX_PLEIN = 1;
      • public void facturer(Client client) {
      • if (client.isEtudiant()) {
      • calculerTotal( TAUX_REDUIT );
      • prelever();
      • }
      • else {
      • calculerTotal( TAUX_PLEIN );
      • prelever();
      • }
      • }
      abstract CommandeEtudiant CommandeStandard
    • 105.
      • CommandeEtudiant
        • public void facturer(Client client) {
        • calculerTotal( TAUX_REDUIT );
        • prelever();
        • }
      • CommandeStandard
        • public void facturer(Client client) {
        • calculerTotal( TAUX_PLEIN );
        • prelever();
        • }
    • 106. Symptômes d’un code intestable
      • Un constructeur cher
      • Des instanciations directes
      • Des blocs statiques
      • Une dynastie de classes
      • Des états globaux
      • Annuaire de service
      • Interroger des collaborateurs
      • Des classes hyperactives
      • Des méthodes trop chargées
      • Mélanger les objets valeurs et les objets services
    • 107. Opération générerFacture entrée sortie Facilement instanciable Getter/Setter Avec un état Objet valeur Est Objet service Fait
    • 108. Objet valeur / Objet métier
      • Objet valeur
        • Facile à instancier
          • Pas de services dans le constructeur
        • Orienté état
        • Probablement pas d’interface
        • Pas de comportement externe
      • Objet service
        • Toujours injecté, jamais instancié
        • Souvent une interface
        • Souvent créateur d’objet valeur
        • Orienté service
        • A mocker
      Client Joueur Expresso BanqueService CommandeValidator BaseDeDonnees
    • 109. Mélanger les objets valeurs et les objets services
      • Pourquoi c’est mal
        • Devoir tout mocker
        • Tests couteux
      • Comment y remédier
        • Externaliser des classes valeurs
        • Faire communiquer les services par des objets valeurs
    • 110. Service Valeur Service Valeur Service
    • 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. Outils
      • Singleton detector
      • Testability explorer
    • 114. Ressources
      • Références
        • Clean code talks , by M.Hevery (Google)
        • Guide « Writing testable code » , by J.Wolter, R.Ruffer, M.Hevery
      • Et aussi….
        • Writing testable code , by Isa Goksu (ThoughWorks)
        • Top 10 things that make code hard to test , by M.Hevery (Google)
        • How to make your code testable , by CodeUtopia
      • Livres
        • xUnit Test Patterns
        • Growing object oriented software
        • Working effectively with legacy code
        • Coder proprement
        • Refactoring