TDD : Test Driven Development 2/2 – Mise en pratique

Dans le post précédent TDD : Test Driven Development 1/2 - Principes, nous avons vu les principes théoriques du Test Driven Development.

Nous allons maintenant mettre en pratique le cycle de développement du TDD.

Pour rappel, les étapes du cycle du TDD sont :

  • Définir la fonctionnalité à l'aide du test => Le test est en échec
  • Implémenter => Le test est en succès, la couverture est à 100%
  • Réécrire / Simplifier => Le test est en succès, la couverture est à 100%

Cycle du TDD

Nous allons voir dans ce post une mise en pratique du TDD avec comme exemple le workflow d'un article d'un blog.

Exemple : workflow d'un article

  • création / enregistrement de l'article
  • transfert pour validation
  • publication de l'article

Voyons comment réaliser la création et l'enregistrement de l'article en TDD.

Fonctionnalité 1 : Création d'un article vide

TDD - Etape 1 : Définir la fonctionnalité

La fonctionnalité est définie par le test qui doit être unitaire.

Nous nous focalisons sur le but de la fonctionnalité qui est ici de créer un article. Nous nous isolons de contexte technique et des autres fonctionnalités à l'aide de "bouchons".

Nous commençons par écrire un test pour définir la fonctionnalité sans écrire de code d'implémentation.

Test : création d'un article

public class CreationArticleTest {
 @Test
 public void testCreationDUnArticleVide() {
   // Given
   ArticleService articleService = new ArticleServiceImpl();
   // When
   Article article = articleService.creerArticleVide();
   // Then
   assertNotNull(article);
 }
}

Nous avons défini le contrat de notre première fonctionnalité : créer un article vide.

Ce test ne compile car il manque les classes d'implémentation.

test en erreur

Dans le cycle du TDD, nous venons de terminer la première étape : définir le contrat de l'implémentation, c'est à dire ce que doit faire notre programme.

TDD - Étape 2 : Définir l'implémentation

Nous passons à la deuxième étape : définir l'implémentation jusqu'à ce que le test passe.

Création des classes, interfaces et méthodes manquantes

Pour cela nous allons nous servir du code écrit dans le test pour créer l'implémentation manquante.

Les classes et les méthodes manquantes sont indiquées en rouge et en erreur par l'IDE eclipse.

Durant cette étape, nous créons ainsi la classe Article, l'interface et la classe d'implémentation du service ArticleService.

Classe Article

public class Article {
}

Interface ArticleService

public interface ArticleService {
 Article creerArticleVide();
}

Classe ArticleServiceImpl

public class ArticleServiceImpl  implements ArticleService {
 public Article creerArticleVide() {
   return new Article();
 }
}

L'exécution du test est maintenant en succès.

test en succès

Le plugin eclemma pour eclipse nous indique que la couverture du test est à 100% : tout ce que nous venons de créer était nécessaire au test, nous n'avons rien ajouté d'inutile pour répondre au contrat de notre fonctionnalité.

Couverture du test

couverture du test

Couverture du projet

La couverture du projet est à 100% :
couverture à 100%

Nous venons de terminer la deuxième étape dans le cycle du TDD : définir l'implémentation jusqu'à ce que le test soit en succès, avec une couverture à 100%.

TDD - Étape 3 : Réécrire le code pour simplifier et réorganiser

Cette étape consiste à simplifier ou à réorganiser le code existant avec les modifications que nous venons d'effectuer.

Ceci peut être par exemple de factoriser du code dupliqué, de simplifier des boucles de traitement ou les conditions des tests.

Par contre il ne s'agit pas de réécrire toute l'implémentation existante. Pour nous limiter dans cette étape, nous ne devons pas modifier les tests existants.

Le code dans notre exemple étant simple, nous n'avons pas besoin d'effectuer de réécriture.

Nous avons terminé la première fonctionnalité et nous pouvons passer à la fonctionnalité suivante.

Fonctionnalité 2 : Enregistrement de l'article

TDD - Étape 1 : Définir la fonctionnalité

Suite à la fonctionnalité de création de l'article, nous souhaitons maintenant enregistrer cet article avec un nom d'auteur, un titre, un contenu. La date d'enregistrement sera définie automatiquement sur l'article.

Nous imposons ce contrat dans notre test unitaire d'enregistrement d'article.

Test : enregistrement d'un article

@Test
public void testEnregistrementDUnArticle() {
 // Given
 Article article = new Article();
 article.setAuteur("test auteur");
 article.setTitre("test titre");
 article.setContenu("test contenu");
 
 ArticleServiceImpl articleServiceImpl = new ArticleServiceImpl();
 ArticleService articleService = articleServiceImpl;

 ArticleDAO articleDAO = Mockito.mock(ArticleDAO.class);
 articleServiceImpl.setArticleDAO(articleDAO);
 
 // When
 articleService.save(article);
 
 // Then
 assertNotNull(article.getDateEnregistrement()); 
 Mockito.verify(articleDAO).save(article);
}

Nous venons d'écrire le contrat correspondant à notre fonctionnalité.

Dans notre test, nous nous sommes focalisés sur l'intention de la fonctionnalité et non sur l'aspect technique. Pour cela nous avons utilisé des "bouchons" à l'aide de "mocks" via Mockito.

Nous nous concentrons ainsi sur le plus important : le code métier qui répond aux règles métier. Nous verrons l'aspect technique plus tard.

Ici la gestion de la persistance de l'article se fera par la DAO. En bouchonnant la DAO, nous ne nous occupons pas de l'implémentation de la DAO. Nous déléguons ceci à plus tard. Nous vérifions uniquement que nous faisons appel à la méthode d'enregistrement de l'article de la DAO avec en paramètre l'article à enregistrer. Nous écrions plus tard, toujours en mode TDD, le contrat de la méthode d'enregistrement de la DAO.

De même si nous avions eu à faire des calculs complexes. Nous aurions bouchonné la classe de calcul et nous aurions uniquement testé que nous avons effectivement fait appel à cette méthode de calcul.

Le test ne compile pas

test en échec
Le code ne compile pas car l'implémentation n'existe pas.

Nous venons de terminer notre première étape dans le cycle du TDD.

TDD - Étape 2 : Définir l'implémentation

Nous passons à la deuxième étape : définir l'implémentation jusqu'à ce que le test passe.

Pour cela nous allons de nouveau nous servir du code écrit dans le test et des fonctionnalités de l'IDE pour créer l'implémentation manquante.

Nous avons ainsi de nouvelles propriétés dans la classe Article, une nouvelle interface ArticleDAO et pour le service ArticleService une nouvelle méthode ainsi que l'injection de la DAO.

Classe Article

public class Article {
 private String auteur;
 private String titre;
 private String contenu;
 private Date dateEnregistrement;
 ...getters / setters...
}

Interface de ArticleDAO

Nous avons uniquement besoin de l'interface pour la DAO pour ne pas nous soucier de l'implémentation.

public interface ArticleDAO {
 void save(Article article);
}

Interface du service ArticleService

public interface ArticleService {
 Article creerArticleVide();
 void saveArticle(Article article);
}

La classe du service ArticleServiceImpl

public class ArticleServiceImpl implements ArticleService {
 private ArticleDAO articleDAO;

 public Article creerArticleVide() {
   return new Article();
 }
 
 public void saveArticle(Article article) {
   article.setDateEnregistrement(new Date());
   articleDAO.save(article);
 }

 public void setArticleDAO(ArticleDAO articleDAO) {
   this.articleDAO = articleDAO;
 }
}

Le code compile, les tests sont maintenant en succès.

Atteindre la couverture à 100%

Cependant la couverture n'est pas à 100% car les méthodes "getXXX" des propriétés de la classe Article ne sont actuellement pas utilisées. Hors, les méthodes getters / setters sur les beans métiers sont obligatoires même si elles ne sont pas utilisées dans l'immédiat.

L'objectif de la couverture à 100% est de vérifier qu'aucun code n'est inutile. C'est pourquoi le test des méthodes getters / setters des beans métiers est un effort à faire pour atteindre la cible des 100%.

Pour cela, les trois lignes suivantes sont ajoutées à la fin du test :

 assertEquals("test auteur",article.getAuteur()); 
 assertEquals("test titre",article.getTitre()); 
 assertEquals("test contenu",article.getContenu()); 

Notre objectif est atteint : la couverture est maintenant à 100%.

Couverture du test

couverture du test

Couverture du projet

La couverture du projet est à 100% :
couverture du projet à 100%

Nous pouvons passer à l'étape 3 du cycle du TDD.

TDD - Étape 3 : Réécrire le code pour simplifier et réorganiser

Le code étant déjà simple, nous n'avons pas besoin de réécrire le code.

Conclusion

Nous avons vu un exemple de mise en pratique du TDD.

Cette démarche change la façon de penser en se focalisant sur les règles fonctionnelles avant de réfléchir à comment les implémenter.

La pratique du TDD demande un temps d'adaptation, mais apporte de nombreux bénéfices.

Vous pouvez par exemple tester le TDD sur l'exemple de ce post pour vous entraîner.

Références

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.