Du code propre qui fonctionne...
maintenant!
Du code propre qui fonctionne maintenant.
public class ConvertisseurAdresseTest { @Test public void uneAdresseComplètePeutSAfficherSurUneLigne() { Adresse adresse = new Adresse(); adresse.setRue("31 chemin de Bénédigue"); adresse.setCodePostal("33400"); adresse.setVille("Talence"); String chaine = new ConvertisseurAdresse(adresse).surUneLigne(); assertThat(chaine).isEqualTo("31 chemin de Bénédigue 33400 Talence"); } }
public class ConvertisseurAdresse { public ConvertisseurAdresse(Adresse adresse) { } public String surUneLigne() { return "31 chemin de Bénédigue 33400 Talence"; } }
public class ConvertisseurAdresse { public ConvertisseurAdresse(Adresse adresse) { this.adresse = adresse; } public String surUneLigne() { return String.format("%s %s %s", this.adresse.getRue(), this.adresse.getCodePostal(), this.adresse.getVille()); } private Adresse adresse; }
@Test public void uneAdresseVideSAfficheVideSurUneLigne() { Adresse adresse = new Adresse(); String chaine = new ConvertisseurAdresse(adresse).surUneLigne(); assertThat(chaine).isEqualTo(""); }
public String surUneLigne() { String rue = this.adresse.getRue() != null ? this.adresse.getRue() + " " : ""; String codePostal = this.adresse.getCodePostal() != null ? this.adresse .getCodePostal() + " " : ""; String ville = this.adresse.getVille() != null ? this.adresse.getVille() : ""; return String.format("%s%s%s", rue, codePostal, ville); }
public String surUneLigne() { List<String> parties = Lists.newArrayList(adresse.getRue(), adresse.getCodePostal(), adresse.getVille()); return Joiner.on(" ").skipNulls().join(parties); }
TDD découple les activités de recherche de solution et respect de l'excellence technique pour les exécuter au moment le plus pertinent.
Des tests orientés résultat ?
[Test] public function impossibleDEcouterUnEvenementPlusieursFois():void { var ecouteur:EcouteurDEvenement = nice(EcouteurDEvenement); _bus.ajouteEcouteur(ecouteur, _evenement); _bus.ajouteEcouteur(ecouteur, _evenement); _bus.evenementSurvenu(_evenement); assertThat(ecouteur, received().method("evenementSurvenu").once()); }
[Test] public function leLancePierreAjusteLeProjectileSiLeToucheSeDeplace():void { _touche.deplaceVers(new Point(13, 37)); _lancePierre.ajusteLeProjectile(_touche); assertThat(_projectile.x, equalTo(13)); assertThat(_projectile.y, equalTo(37)); }
Les doublures de test sont un mal parfois nécessaire.
Problème de test = problème de code de prod.
Un innocent recruteur saisit la description d'offre :
“Vous serez en charge d'un enfant de 5 ans en périscolaire du matin de 6h45 à 7h45 ou en périscolaire du soir de 18h30 à 20h30, 3 à 4 fois par semaine en fonction du planning déterminé par les parents.”Il obtient le message d'erreur :
“Le numéro de téléphone que vous avez saisi est surtaxé. Nous vous remercions d'indiquer un numéro au tarif local ou un numéro de téléphone mobile.”Un indice :
“Vous serez en charge d'un enfant de 5 ans en périscolaire du matin de 6h45 à 7h45 ou en périscolaire du soir de 18h30 à 20h30, 3 à 4 fois par semaine en fonction du planning déterminé par les parents.”Une regex trop envahissante :
public class DetecteurDeNumeroDeTelephone { private static final Pattern PATTERN_TELEPHONE = Pattern.compile("(\\d(.){0,3}){9}\\d"); public List<String> detecte(String texte) { List<String> numeros = Lists.newArrayList(); Matcher m = PATTERN_TELEPHONE.matcher(texte); ... } }
@Test public void lEnsembleDesNumerosSontDetectesDansUnTexte() { String texte = "Voici mon numéro : 06 21 03 43 22, " + "vous pouvez me joindre au travail : 05-22-34-11-45"; List<String> numeros = this.detecteur.detecte(texte); assertThat(numeros).containsExactly("0621034322", "0522341145"); }
@Test public void unNumeroNePeutPasAvoirDesChiffresSeparesPar3Caracteres() { List<String> numeros = this.detecteur.detecte("04---04-04-04---04"); assertThat(numeros).isEmpty(); }
@Test public void unCalendrierPeutDireSiUnJourEstOuvré() { Calendrier calendrier = new Calendrier(); Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); assertThat(calendrier.estOuvré(calendar.getTime())).isTrue(); calendar.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY); assertThat(calendrier.estOuvré(calendar.getTime())).isFalse(); calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); assertThat(calendrier.estOuvré(calendar.getTime())).isFalse(); }
En cas d'erreur :
org.junit.ComparisonFailure: Expected: false, Actual: true at unCalendrierPeutDireSiUnJourEstOuvré(TestCalendrier.java:23)
@Test public void unJourDeLaSemaineDeTravailEstOuvré() { compareOuvréPourLeJourEtLaValeur(Calendar.MONDAY, true); } @Test public void leSamediNEstPasOuvré() { compareOuvréPourLeJourEtLaValeur(Calendar.SATURDAY, false); } @Test public void leDimancheNEstPasOuvré() { compareOuvréPourLeJourEtLaValeur(Calendar.SUNDAY, false); } private void compareOuvréPourLeJourEtLaValeur(int jour, boolean attendu) { boolean ouvré = new Calendrier().estOuvré(créeJour(jour)); assertThat(ouvré).as("Jour ouvré").isEqualTo(attendu); }
En cas d'erreur :
org.junit.ComparisonFailure: [Jour ouvré] Expected: false, Actual: true leDimancheNEstPasUnJourOuvré(CalendrierTest.java:24)
Le test doit rester au service du développeur.
public class PublieurArticleTest { @Test public void laDateDePublicationDUnArticleEstLaDateCourante() { Article article = new Article(); new PublieurArticle().publie(article); Date datePublication = article.getDatePublication(); assertThat(datePublication).isEqualTo(new Date()); } }
En cas d'erreur :
org.junit.ComparisonFailure: expected: java.lang.String<2013-11-17T18:42:50> but was: java.lang.String<2013-11-17T18:42:50> Expected :2013-11-17T18:42:50 Actual :2013-11-17T18:42:50 at laDateDeCréationDUnArticleEstLaDateCourante(PublieurArticleTest.java:18)
public class PublieurArticle { public void publie(Article article) { article.setDatePublication(new Date()); } }
@Test public void laDateDePublicationDUnArticleEstLaDateCourante() { ... assertThat(datePublication).isEqualTo(new Date()); }
La date courante a évoluée!
public class PublieurArticleTest { @Test public void laDateDePublicationDUnArticleEstLaDateCourante() { Article article = new Article(); new PublieurArticle().publie(article); Date datePublication = article.getDatePublication(); assertThat(datePublication).is(plusOuMoinsMaintenant()); } }
@Test public void laDateDePublicationDUnArticleEstLaDateCourante() { Article article = new Article(); new PublieurArticle(créeTempsFigé(NOEL)).publie(article); Date datePublication = article.getDatePublication(); assertThat(datePublication).isEqualTo(NOEL); } private Temps créeTempsFigé(final Date date) { return new Temps() { @Override public Date maintenant() { return date; } }; }
public class PublieurArticle { public PublieurArticle(Temps temps) { this.temps = temps; } public void publie(Article article) { article.setDatePublication(temps.maintenant()); } private Temps temps; }
public interface Temps { Date maintenant(); }
@Test public void leFournisseurPeutFournirUnElémentAléatoirement() { List<String> chaines = Lists.newArrayList("un", "deux", "trois"); GenerateurNombre générateur = new GenerateurNombreFige(1); FournisseurElement fournisseur = new FournisseurElement(générateur); String élément = fournisseur.fournisUnElémentDeLaListe(chaines); assertThat(élément).isEqualTo("deux"); }
Les tests doivent rester fiables qu'importe l'environnement.
Tests contraignants =>
Moins de tests =>
Moins de feedbacks =>
Moins de courage =>
Moins de refactoring =>
Code moins évolutif =>
Moins d'agilité
Les mêmes exigences que pour le code de production !
Un test n'est que du code standard qui a pour seule particularité de ne pas partir en production.
Peut-on tester les méthodes privées ?
Non-sens !
Peut-on mocker les méthodes statiques ?
Non-sens !
... et bien autres choses
Fin