Top Banner
Comment écrire du code testable Conférence Agile France 2010 Florence CHABANOIS
114

Comment écrire du code testable ?

Aug 20, 2015

Download

Technology

Fou Cha
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Comment écrire du code testable ?

Comment écrire du code testable

Conférence Agile France 2010 Florence CHABANOIS

Page 2: Comment écrire du code testable ?
Page 3: Comment écrire du code testable ?
Page 4: Comment écrire du code testable ?

Frein ?

Direction assistée?

Boite de vitesse ?

Pneus défectueux ?

Page 5: Comment écrire du code testable ?
Page 6: Comment écrire du code testable ?

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

Page 7: Comment écrire du code testable ?

Isolation

Pour permettre la séparationExternaliser les dépendancespublic Moteur() { reservoirHuile = new ReservoirHuilePlein(); }

public MoteurOk(ReservoirHuile reservoirHuile) { this.reservoirHuile = reservoirHuile;}

Bannir les dépendances cachées

Page 8: Comment écrire du code testable ?

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

Page 9: Comment écrire du code testable ?

Le TDD ne donne pas l’immunité

2. Le code peut être testé etClasses et méthodes fourre-toutLes tests souffrent• Performances (constructeur couteux)• Compréhensibilité (tests = spécifications)

Le développeur aussi• Tests lents• Maintenabilité

Page 10: Comment écrire du code testable ?

Leitmotiv

• Deux lignes de conduite

Isolabilité Simplicité

Page 11: Comment écrire du code testable ?
Page 12: Comment écrire du code testable ?

Symptômes d’un code intestable

Isolabilité Simplicité

Classes hyperactives

Méthodes chargées

Interroger des collaborateurs

Etats globauxAnnuaires

Blocs statiques

Instanciationdirecte

Constructeur cher

Mélangerservice et valeur

Héritage

Page 13: Comment écrire du code testable ?

Ennemis jurés

Page 14: Comment écrire du code testable ?
Page 15: Comment écrire du code testable ?

public class Dictionnaire { private Map<String, String> definitions = new HashMap<String,

String>();

public Dictionnaire() throws IOException { File file = new File("francais.txt"); BufferedReader reader = new BufferedReader(new

FileReader(file)); String ligne; while ((ligne = reader.readLine()) != null) { String[] tableauLigne = ligne.split(":"); definitions.put(tableauLigne[0], tableauLigne[1]); } }

public String getDefinition(String mot) { return definitions.get(mot); }

Page 16: Comment écrire du code testable ?

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.

Page 17: Comment écrire du code testable ?

Tester getDefinition()

@Test public void testGetDefinition() throws IOException { Dictionnaire dico = new Dictionnaire(); String returnedDefinition = dico.getDefinition("test"); assertThat(returnedDefinition, is(equalTo("Opération

destinée à etc."))); }

Page 18: Comment écrire du code testable ?

public Dictionnaire() throws IOException { File file = new File("francais.txt"); BufferedReader reader = new BufferedReader(new

FileReader(file)); String ligne; while ((ligne = reader.readLine()) != null) { String[] tableauLigne = ligne.split(":"); definitions.put(tableauLigne[0], tableauLigne[1]); } }

Test très lentObligé d’avoir un fichier

Page 19: Comment écrire du code testable ?

@Test public void testGetDefinition_WhenMotNonTrouve()

throws IOException {Dictionnaire dico = new Dictionnaire();(…)

} @Test public void testGetDefinition_WhenMotNonValide()

throws IOException {Dictionnaire dico = new Dictionnaire();(…)

}

Page 20: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher

Page 21: Comment écrire du code testable ?

Un constructeur trop cher

Pourquoi c’est mal On ne peut PAS éviter d’instancier une classe pour la testerEnlève une veine (seam)

Test pas isoléTest potentiellement couteuxDifficile de simuler un autre comportement

Voire plus, si c’est utilisé par d’autres tests

Page 22: Comment écrire du code testable ?

Un constructeur trop cher

Signes d’alertesIf, switch, loopnew d’objets Des appels statiques

… En fait autre chose que des assignations d’attributs

Page 23: Comment écrire du code testable ?

Un constructeur trop cher

Comment y remédierMéthode init() à appeler après le constructeur

Pas de test dessus

Un constructeur spécial pour le testDéplacement du problème

Extraire dans une autre méthode, qu’on surcharge

Page 24: Comment écrire du code testable ?

Code : Extraction de la méthode

public DictionnairePatche() throws IOException { initialize(); }

protected void initialize() throws FileNotFoundException, IOException

{ File file = new File("francais.txt"); BufferedReader reader = new BufferedReader(new

FileReader(file)); String ligne; while ((ligne = reader.readLine()) != null) { String[] tableauLigne = ligne.split(":"); definitions.put(tableauLigne[0], tableauLigne[1]); } }

Page 25: Comment écrire du code testable ?

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("test"); assertThat(returnedDefinition, is(equalTo("Opération destinée à

contrôler le bon fonctionnement d'un appareil ou la bonne exécution d'un programme dans son ensemble.")));

}

Page 26: Comment écrire du code testable ?

Un constructeur trop cher

Comment y remédierMéthode init() à appeler après le constructeur

Pas de test dessus

Un constructeur spécial pour le testDéplacement du problème

Extraire dans une autre méthode, qu’on surcharge

Pas de test dessus

Page 27: Comment écrire du code testable ?

Un constructeur trop cher

Signes d’alertes mis à jourIf, switch, loopnew d’objets Des appels static…. En fait autre chose que des assignations d’attributsUn constructeur spécial testInit() Du code spécial test : @VisibleForTesting

Page 28: Comment écrire du code testable ?

Un constructeur trop cher

Comment y remédierMéthode init() Un constructeur spécial pour le testExtraire dans une autre méthode, qu’on surcharge

Comment y remédier mieuxFaire des constructeurs relais uniquementPasser les collaborateurs prêts en paramètres au lieu de les créer

Injection de dépendancesFactories

Page 29: Comment écrire du code testable ?

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é »

Page 30: Comment écrire du code testable ?

public class DictionnaireFactory { public static Dictionnaire buildFromTextFile() throws

IOException { Map<String, String> definitions = new HashMap<String,

String>(); File file = new File("francais.txt"); BufferedReader reader = new BufferedReader(new

FileReader(file)); String ligne; while ((ligne = reader.readLine()) != null) { String[] tableauLigne = ligne.split(":"); definitions.put(tableauLigne[0], tableauLigne[1]); } return new DictionnaireTestable(definitions); }} Séparation des

responsabilités

Page 31: Comment écrire du code testable ?

Principe de responsabilité unique

Page 32: Comment écrire du code testable ?

Principe de responsabilité unique

1. Je cherche sur Internet de quelle matière première j’ai besoin pour en fabriquer

2. J’appelle Air France pour réserver un billet d’avion et aller en chercher en Chine

3. Je demande au service Bureautique de m’en installer un nouveau

Page 33: Comment écrire du code testable ?

Principe de responsabilité unique

Et le service Bureautique ?1. Cherche sur Internet de quelle matière

première il a besoin pour en fabriquer2. Appelle Air France pour réserver un billet

d’avion et aller en chercher en Chine3. Le commande chez son fournisseur

Page 34: Comment écrire du code testable ?

Principe de responsabilité unique

Etudes

Fournisseur

Bureautique

Page 35: Comment écrire du code testable ?

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

Page 36: Comment écrire du code testable ?

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(); }

Page 37: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes

Page 38: Comment écrire du code testable ?

Des instanciations directes

Pourquoi c’est malCouplage fortEnlève une veine (seam)

Test pas isoléTest potentiellement couteuxDifficile de simuler un autre comportement

Page 39: Comment écrire du code testable ?

Des instanciations directes

Signes d’alertesDes « new » dans une classe autre que Factory ou Builder

Page 40: Comment écrire du code testable ?

Des instanciations directes

Comment y remédierFramework de mocks : JMockit, Powermock

Comment y remédier mieuxPasser les objets nécessaires en paramètres de la méthodeSéparer construction du graphe d’objets de la logique métier

Injection de dépendancesFactories

Page 41: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques

Page 42: Comment écrire du code testable ?

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) {

}}

Page 43: Comment écrire du code testable ?

Des blocs statiques

Pourquoi c’est malCouplage très fort

Pas possible de le remplacer par un mockNi de le surcharger dans les testsPotentiellement très couteux

Effets de bord entre des tests censés être isolésLe test passe, parfoisEtat permanent

Page 44: Comment écrire du code testable ?

Des blocs statiques

Signes d’alertesStatic {}Un test qui ne fonctionne plus au sein d’une suite

Comment y remédierSupprimer tous les bloc statiques et introduire des classesPasser les collaborateurs en paramètres au lieu de les créer

Injection de dépendancesFactories

Page 45: Comment écrire du code testable ?

public class JoueurTestable { private Plateau plateau; public JoueurTestable(Plateau plateau) { this.plateau = plateau; } public void joindre(Partie partie) { (…) }}

Page 46: Comment écrire du code testable ?

Spring-jeu.xml

<bean class="fr.soat.agileconference2010.blocstatic.JoueurTestable" id="joueur1" scope="prototype">

<constructor-arg ref="plateau"></constructor-arg></bean>

<bean class="fr.soat.agileconference2010.blocstatic.metier.PlateauCommercial" id="plateau" scope="singleton"></bean>

Page 47: Comment écrire du code testable ?

testJoindre()

Plateau plateau = new PlateauDeDemo(); JoueurTestable joueur = new

JoueurTestable(plateau); joueur.joindre(new Partie()); //Verifications

Page 48: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes

Page 49: Comment écrire du code testable ?

Des dynasties de classes

?

Page 50: Comment écrire du code testable ?
Page 51: Comment écrire du code testable ?

Des dynasties de classes

Pourquoi c’est malCouplage fort avec classe mèreLenteurFragilitéTests plus difficiles à maintenir (redondance)

Page 52: Comment écrire du code testable ?

Des dynasties de classes

Signes d’alertesQuand le code devient difficile à tester Quand les tests sont redondants / difficile à maintenir à cause de la classe mère

Comment y remédierUtiliser la composition pour réutiliser du codeLimiter l’héritage aux besoins de polymorphisme

Page 53: Comment écrire du code testable ?
Page 54: Comment écrire du code testable ?
Page 55: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes5. Des états globaux

Page 56: Comment écrire du code testable ?

a = new X().traiter();

b = new X().traiter();

a = b ?

Page 57: Comment écrire du code testable ?

Source : http://misko.hevery.com/2009/10/07/design-for-testability-talk/

Page 58: Comment écrire du code testable ?
Page 59: Comment écrire du code testable ?

On veut poser un test sur l’expresso

public class MachineACafe {public void payer(float montant){(…)}

public Expresso preparerExpresso() {(…)}

public void brancher(){(…)}

}

Page 60: Comment écrire du code testable ?

Test de l’expresso

@Test public void testPreparerExpresso() { MachineACafe machineACafe = new MachineACafe();

Expresso expresso = machineACafe.preparerExpresso(); assertThat(expresso.estConforme(), is(true));

}

Null Pointer Exception

Page 61: Comment écrire du code testable ?

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

Page 62: Comment écrire du code testable ?

Test de l’expresso

@Test public void testPreparerExpressoEssai3() { MachineACafe machineACafe = new MachineACafe();

BaseDeDonnees baseDeDonnees = new BaseDeDonnees();

baseDeDonnees.init("myUrl", "myLogin", "myPassword");

machineACafe.setBaseDeDonnees(baseDeDonnees); Expresso expresso = machineACafe.preparerExpresso(); assertThat(expresso.estConforme(), is(true)); }

Null Pointer Exception

Page 63: Comment écrire du code testable ?

Test de l’expresso

@Test public void testPreparerExpressoEssai4() { MachineACafe machineACafe = new MachineACafe();

BaseDeDonnees baseDeDonnees = new BaseDeDonnees();

baseDeDonnees.init("myUrl", "myLogin", "myPassword");

baseDeDonnees.setNotificateur(new Notificateur()); machineACafe.setBaseDeDonnees(baseDeDonnees); Expresso expresso = machineACafe.preparerExpresso(); assertThat(expresso.estConforme(), is(true)); }

CafeException

Page 64: Comment écrire du code testable ?

Pourquoi CafeException ?

public void verifierPreconditions() { if (!robinetActive()) { final String erreur = "Vérifier le robinet"; baseDeDonnees.logguerErreur(this, erreur); throw new CafeException(erreur); }

Hein, quel robinet ?

Page 65: Comment écrire du code testable ?

Pourquoi CafeException ?

private boolean robinetActive() { Robinet robinet = Robinet.getInstance(); return (robinet.estOuvert() &&

robinet.estConnecte(this)); }

Page 66: Comment écrire du code testable ?

Test de l’expresso

@Test public void testPreparerExpressoEssai5() { MachineACafe machineACafe = new MachineACafe(); final BaseDeDonnees baseDeDonnees = new

BaseDeDonnees(); baseDeDonnees.init("myUrl", "myLogin",

"myPassword"); baseDeDonnees.setNotificateur(new Notificateur()); machineACafe.setBaseDeDonnees(baseDeDonnees); Robinet.getInstance().ouvrir(); Expresso expresso = machineACafe.preparerExpresso(); assertThat(expresso.estConforme(), is(true)); }

Ok !Ok !

Page 67: Comment écrire du code testable ?

Je devJe dev

Je devJe dev

Je devJe dev

Je devJe dev

Mon code

Il devIl dev

Il dev COMMITIl dev

Il devIl dev

Il devIl dev

COMMIT

Son code

Page 68: Comment écrire du code testable ?

Boom

public void testPreparerVerreEau_whenDefaultValues() {

FontaineAEau fontaine = new FontaineAEau(); VerreEau verre = fontaine.preparerVerreEau(); assertThat(verre, is(nullValue())); }}

AssertionError : expected NULL

« Son code »

Page 69: Comment écrire du code testable ?

Test de l’expresso

@Test public void testPreparerExpressoEssai6() { MachineACafe machineACafe = new MachineACafe(); final BaseDeDonnees baseDeDonnees = new

BaseDeDonnees(); baseDeDonnees.init("myUrl", "myLogin", "myPassword"); baseDeDonnees.setNotificateur(new Notificateur()); machineACafe.setBaseDeDonnees(baseDeDonnees); Robinet.getInstance().ouvrir(); Expresso expresso = machineACafe.preparerExpresso(); assertThat(expresso.estConforme(), is(true)); Robinet.getInstance().fermer(); }

Okpour le moment…

Page 70: Comment écrire du code testable ?

Des états globaux

Pourquoi c’est malMensonge : « il n’y a pas de dépendances. »

Méthode statique ou Singleton = dépendance cachée.

Pas de veine pour placer un mockTest pas isoléTest potentiellement couteuxDifficile de simuler un autre comportement

Risque de perturbations avec d’autres testsEtat présumé Plus longs à lancerDébogage difficile

Page 71: Comment écrire du code testable ?

Des états globaux

Signes d’alertesDes singletonsDu 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

Page 72: Comment écrire du code testable ?

Des états globaux

Comment y remédierSuppression 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é

Page 73: Comment écrire du code testable ?

Des états globaux

Signes d’alertes mis à jourDes singletonsDu 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 groupeou vice versa

Du code spécial testDes setters, reset, init dans les singletons@VisibleForTesting

Page 74: Comment écrire du code testable ?

Des états globaux

Comment y remédier réellementBannir singleton et code staticDécliner en classesInjection de dépendances

Page 75: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes5. Des états globaux6. Annuaire de service

Page 76: Comment écrire du code testable ?

Annuaire de services

public Maison(Locator locator) { porte = locator.getPorte(); fenetre = locator.getFenetre(); toit = locator.getToit(); }

Page 77: Comment écrire du code testable ?

Annuaire de services

Pourquoi c’est malTromperie

« il n’y a pas de dépendances »« il n’y en a qu’une seule »

Application entière à initialiser

Page 78: Comment écrire du code testable ?

Annuaire de services

Signes d’alertes« Registry », « context », « locator »

Comment y remédierPasser les objets réellement utilisésInjection de dépendances

Page 79: Comment écrire du code testable ?

Pollueurs

Page 80: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes5. Des états globaux6. Annuaire de service7. Interroger des collaborateurs

Page 81: Comment écrire du code testable ?
Page 82: Comment écrire du code testable ?

Avoir des intermédiaires

public void facturer(Commande commande, Client client) {

banqueService.prelever(client.getCompteBancaire(), commande.getTotal());

emailService.notifierPrelevement(client.getEmail()); }

Page 83: Comment écrire du code testable ?

Avoir des intermédiaires

Pourquoi c’est malTromperie : « on a besoin de Commande et Client »

Couplage fort avec l’objet intermédiaireLisibilité Débogage plus complexe (exception)Initialisation du test plus complexe

Page 84: Comment écrire du code testable ?

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 mocksDevoir mocker des getters/setters

Page 85: Comment écrire du code testable ?

Avoir des intermédiaires

Comment y remédierAppliquer 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

Page 86: Comment écrire du code testable ?

public void facturer(CompteBancaire compte, double montant, String email) {

banqueService.prelever(compte, montant); emailService.notifierPrelevement(email); }

Page 87: Comment écrire du code testable ?

Initialisation du test avant

Client client = new Client(); final CompteBancaire compte = new

CompteBancaire(); client.setCompteBancaire(compte); final String email = "[email protected]"; client.setEmail(email);

Commande commande = new Commande(); final double total = 20.0; commande.setTotal(total);

// Whenmanager.facturer(commande, client);

Page 88: Comment écrire du code testable ?

Initialisation du test après

final CompteBancaire compte = new CompteBancaire();

final String email = "[email protected]"; final double montant = 20.0;

// Whenmanager.facturer(compte, montant, email);

Page 89: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes5. Des états globaux6. Annuaire de service7. Interroger des collaborateurs8. Des classes hyperactives

Page 90: Comment écrire du code testable ?

CommandeManager

Page 91: Comment écrire du code testable ?

Des classes hyperactives

Pourquoi c’est malClasse fourre-toutPeu robuste aux changementsLisibilitéMaintenabilité

Page 92: Comment écrire du code testable ?

Des classes hyperactives

Signes d’alertes« manager », « utils », « helper »Qu’est ce qu’elle fait? EtPas évidente à comprendre pour un nouvel arrivant / Pas facile d’avoir en tête ce qu’elle fait en une foisDifficile de trouver un nom à la classeQuand un champ n’est utilisé que par quelques méthodesBeaucoup de champs et/ou collaborateursBeaucoup de méthodesMéthodes avec peu de rapport les unes les autresMéthodes statiques

Page 93: Comment écrire du code testable ?

Des classes hyperactives

Comment y remédierEtapes1. Identifier les responsabilités de la classe2. Les nommer3. Les extraire dans autant de classes4. Une classe peut avoir le rôle d’orchestrerComment identifier les responsabilités?

Repérer les méthodes qui ne sont utilisées que par un ou quelques champsRepérer les méthodes statiques et les rendre à leur paramètres (ou wrapper de paramètres)• listerCommandes(Client client)

Regrouper méthodes qui se ressemblentRegrouper les attributs souvent utilisés ensemble

Page 94: Comment écrire du code testable ?

Des classes hyperactives

Comment y remédier (suite)Si code legacy

Extraire une classe pour chaque modification / nouvelle fonctionnalité

Page 95: Comment écrire du code testable ?

Des classes hyperactives

Comment y remédier (suite)Si code legacy

Extraire une classe pour chaque modification / nouvelle fonctionnalité

Imbriquer les collaborateurs

A

YZX

W A

Y ZX

W

Page 96: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes5. Des états globaux6. Annuaire de service7. Interroger des collaborateurs8. Des classes hyperactives9. Des méthodes trop chargées

Page 97: Comment écrire du code testable ?

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; }

Page 98: Comment écrire du code testable ?

Des méthodes trop chargées

Pourquoi c’est malAugmente la complexité des testsTrès sensible aux modificationsDifficile de comprendre tout de suite le fonctionnement

Page 99: Comment écrire du code testable ?

Des méthodes trop chargées

Signes d’alertesSi ça dépasse l’écranS’il y a des ifs, switch, loop….

Plus d’un && ou ||If/else imbriquésCheck NULL

Des commentaires sont nécessaires pour expliquer la logiqueUne complexité élevée (cf sonar)

Page 100: Comment écrire du code testable ?

Des méthodes trop chargées

Comment y remédierDécouper en plusieurs autres méthodesExtraire d’autres classes et déléguerFavoriser le polymorphismeRetourner des objets vides plutôt que des NULLDonner des valeurs par défaut (pour éviter un else)

Page 101: Comment écrire du code testable ?

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)

Page 102: Comment écrire du code testable ?

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(); }

Page 103: Comment écrire du code testable ?

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; }

Page 104: Comment écrire du code testable ?

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

Page 105: Comment écrire du code testable ?

CommandeEtudiantpublic void facturer(Client client) { calculerTotal(TAUX_REDUIT); prelever();}

CommandeStandard public void facturer(Client client) { calculerTotal(TAUX_PLEIN); prelever(); }

Page 106: Comment écrire du code testable ?

Symptômes d’un code intestable

1. Un constructeur cher2. Des instanciations directes3. Des blocs statiques4. Une dynastie de classes5. Des états globaux6. Annuaire de service7. Interroger des collaborateurs8. Des classes hyperactives9. Des méthodes trop chargées10. Mélanger les objets valeurs et les objets services

Page 107: Comment écrire du code testable ?

OpérationgénérerFacture

entrée sortie

Facilement instanciableGetter/SetterAvec un état

Objet valeurEst

Objet serviceFait

Page 108: Comment écrire du code testable ?

Objet valeur / Objet métier

Objet valeur Facile à instancier

Pas de services dans le constructeurOrienté étatProbablement pas d’interfacePas de comportement externe

Objet service Toujours injecté, jamais instanciéSouvent une interfaceSouvent créateur d’objet valeurOrienté serviceA mocker

ClientJoueur

Expresso

BanqueServiceCommandeValidator

BaseDeDonnees

Page 109: Comment écrire du code testable ?

Mélanger les objets valeurs et les objets services

Pourquoi c’est malDevoir tout mockerTests couteux

Comment y remédierExternaliser des classes valeursFaire communiquer les services par des objets valeurs

Page 110: Comment écrire du code testable ?

Service

Valeur

ServiceValeur

Service

Page 111: Comment écrire du code testable ?

Symptômes d’un code intestable

Isolabilité Simplicité

Classes hyperactives

Méthodes chargées

Interroger des collaborateurs

Etats globauxAnnuaires

Blocs statiques

Instanciationdirecte

Constructeur cher

Mélangerservice et valeur

Héritage

Page 112: Comment écrire du code testable ?

Vers du code testable

Isolabilité Simplicité

Passer les objets utilisésDirectement 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 veinespour 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éthodesPolymorphisme

Page 113: Comment écrire du code testable ?

Outils

Singleton detectorTestability explorer

Page 114: Comment écrire du code testable ?

Ressources

RéférencesClean 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

LivresxUnit Test PatternsGrowing object oriented softwareWorking effectively with legacy code

Coder proprementRefactoring