Successfully reported this slideshow.

Comment écrire du code testable ?

6 082 vues

Publié le

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

Publié dans : Technologie, Formation
  • Soyez le premier à commenter

Comment écrire du code testable ?

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

×