Le Test Driven Development (TDD)

Le TDD par l'exemple

Le but de ce tutoriel est de montrer le développement d’une nouvelle fonctionnalité en TDD. Pour commencer nous allons utiliser un cas simple le codage de la suite de Fibonacci en mode console sans utiliser la récursivité.

La suite de Fibonacci est un classique de la programmation.


Wikipedia :

La suite de Fibonacci est une suite d'entiers dans laquelle chaque terme est la somme des deux termes qui le précèdent. Elle commence généralement par les termes 0 et 1 (parfois 1 et 1) et ses premiers termes sont : 0, 1, 1, 2, 3, 5, 8, 13, 21


Dans ce tutoriel le but est de créer une méthode qui retourne le n-ième terme de la suite.Si on appelle la fonction avec 4 en paramètre on veut qu’elle retourne 3.

Le but n’est pas d’arriver à résoudre ce problème au niveau algorithmique mais d'expliquer la démarche pour développer cette fonctionnalité en utilisant le développement guidé par le test (TDD).

Le TDD utilise principalement les tests unitaires.

Les tests unitaires

Les tests unitaires permettent de vérifier que chaque partie du code se comporte comme prévue. Les tests unitaires s’assurent qu’en cas de modification ultérieure du code le résultat obtenu sera toujours le même.

Les tests unitaires doivent être :

Isolés: les tests doivent pouvoir s’exécuter indépendamment les uns des autres.

Répétables : Les tests doivent toujours retourner le même résultat quel que soit le nombre de fois où ils sont exécutés.

Étendus : les tests doivent couvrir chaque partie de code de votre application. Atteindre les 100% de couverture de test est néanmoins quasiment impossible, 80% est un bon chiffre.

Rapides : les tests doivent s’exécuter rapidement.

Simples : les tests ne doivent pas tester plusieurs fonctionnalités en même temps.

Correctement nommés : ils existent plusieurs conventions de nommage de tests. Le nom devient très important si vous avez un grand nombre de tests. Il ne faut pas hésiter à donner un nom très long au test. Exemple de nommage. On peut aussi organiser ses tests en différents groupes.

Installation des outils

XUnit

Pour pouvoir gérer efficacement les tests unitaires il est absolument nécessaire d’installer un framework de test. Ici nous utilisons XUnit.

Si vous ne l’avez jamais utilisé vous pouvez suivre ce tutoriel très complet.

Nous n’aurons pas besoin de la console Xunit (xUnit.net console runner), nous lancerons directement les tests dans Visual Studio. (xunit.runner.visualstudio).

NCrunch

Nous allons aussi utiliser NCrunch. Avec cet outil vous n’aurez pas besoin de lancer vos tests unitaires, ils sont automatiquement lancés en tâche de fond. Ncrunch permet aussi de déterminer les parties de code qui sont couvertes par des tests.

C’est un très bel outil, il est payant et assez cher. Vous pouvez profiter des 30 jours gratuits.

Installation de NCrunch.

ReSharper

Resharper est un excellent outil qui s’ajoute à Visual Studio, il permet d’écrire du code de qualité, il fait tellement de choses qu’il est difficile de le décrire mais une fois qu’on l’a installé on ne peut plus s’en passer.

Reshaper est très utile pour faire du refactoring de code.

Resharper est lui aussi payant, vous pouvez le tester pendant 30 jours gratuitement.

Installation de Resharper

Il est difficile de travailler en TDD sans de bons outils.

Les principes fondamentaux du Test Driven Development

Les 3 règles de base d’Uncle Bob

Vous n’avez pas le droit d’écrire une ligne de code de production si ce n’est pour faire passer un test d’échec à réussite.

Vous n’avez pas le droit d’écrire plus d’un test unitaire en échec, une erreur de compilation est un échec.

Vous n’avez pas le droit d’écrire plus de code de production que nécessaire pour faire passer le test d’échec à réussite.


Uncle Bob est le surnom de Robert Cecil Martin le grand gourou des méthodes Agiles et du TDD. Les règles de base sont très importantes si vous voulez faire du TDD correctement.


Le test doit obligatoirement passer par 3 états

Rouge : écrire un petit test qui ne fonctionne pas

Vert : Faire passer le test au vert en écrivant juste le code nécessaire

Refactoring : Modifier le code pour améliorer le design en s’assurant que les tests passent toujours.


L’aspect dynamique est très important en TDD. Vous devez sans cesse faire passer vos tests de l’état fail (rouge) à pass (vert).

Il faut écrire le code minimal pour que le test que l’on vient d’écrire passe.


Exemple de mauvais TDD avec la suite de Fibonacci :

1) Ecrire le test qui teste si le 2ème terme de la suite est 1.

2) Coder la suite de Fibonacci complète

Au départ le test sera en échec puis le test va passer au vert mais vous n’avez pas écrit le code minimal pour que le test passe. Tous vos autres tests pour les autres termes de la suite vont être verts tout de suite.

Réalisation de la suite de Fibonacci en TDD

Ecriture du premier test

[Theory]
[InlineData(0, 0)]
public void TestFibonacciNumberForTerm0(int value, int expectedValue)
{
            Fibonacci fibo = new Fibonacci();
            var result = fibo.Calcul(value);
            Assert.Equal(expectedValue, result);
}

Value va contenir la première valeur contenue dans InlineData (0), expectedValue va contenir la deuxième valeur (0). Cela permet d’avoir des tests paramétrés avec XUnit.

La classe Fibonacci n’est bien sûr pas définie ainsi que la méthode Calcul, Visual Studio peut les générer directement en cliquant sur « actions rapides » puis générer la classe et la méthode. Vous pouvez aussi utiliser Resharper qui est plus puissant.

On peut être amené à modifier le code généré automatiquement par Visual Studio s’il ne convient pas.

On peut aussi commencer à générer les classes dans le projet de test pour plus de simplicité.


Ecriture du 1er code de l’exercice généré par Visual Studio

public class Fibonacci
{      
        public int Calcul(int v)
        {
            throw new NotImplementedException();
        }       
 }

NCrunch m’indique que le test est fail (rouge). C’est normal. Je le fais passer au vert sans écrire plus de code que nécessaire (voir ci-dessous)

public class Fibonacci
{       
        public int Calcul(int v)
        {
            return 0;
        }        
}

J’ai écrit mon code et le test est passé au vert. Je ne fais pas de refactoring donc j’écris un nouveau test.


Ecriture du deuxième test

[Theory]
[InlineData(1,1)]
public void TestFibonacciNumberForTerm1(int value, int expectedValue)
{
            Fibonacci fibo = new Fibonacci();
            var result = fibo.Calcul(value);
            Assert.Equal(expectedValue, result);
}

J’aurais pu utiliser le premier test en ajoutant un nouveau cas avec InlineData mais je préfère créer un deuxième test pour cet exemple.

Le deuxième test ne passe pas. Normal, le code de production ne calcule pas le premier terme de la suite.


Ecriture du deuxième code de l’exercice

public class Fibonacci
{      
        public int Calcul(int v)
        {
            return v;
        }        
}

Là aussi je n’écris pas le code complet mais juste le code suffisant pour faire passer le test. Le test passe au vert.


Ecriture du troisième test

[Theory]
[InlineData(6, 8)]
public void TestFibonacciNumberForTerm6(int value, int expectedValue)
{
            Fibonacci fibo = new Fibonacci();
            var result = fibo.Calcul(value);
            Assert.Equal(expectedValue, result);
}

Je recherche désormais la valeur pour le terme 6.

Le test ne passe pas.


Ecriture du troisième code de l’exercice

public class Fibonacci
{
        public int Calcul(int v)
        {
            if (v < 2) return v;
            int a = 0;
            int b = 1;
            int c = 0;
            for (int i = 1; i < v; i++)
            {
                c = a + b;
                a = b;
                b = c;
            }
            return c;
        }
}

Désormais la classe contient le code complet et tous les tests passent au vert.


L’exemple est terminé. Si vous suivez les règles du TDD vous ne pouvez pas ajouter d’autres tests car aucun test ne sera en échec au départ.


Conclusion : Travailler en TDD est plus complexe qu’il n’y parait de prime abord, il y a un aller-constant entre les tests et le code, il faut toujours faire passer les tests du rouge au vert. Le résultat final est similaire à un test unitaire écrit après le code de production. Les différentes étapes pour construire le test sont aussi importantes que le test lui-même.


D’après certaines études développer en TDD augmente le temps de développement de 30% mais permet d’avoir un logiciel beaucoup plus robuste. Comme toujours la qualité à un prix, avoir un logiciel de qualité demande plus de temps donc d’argent. Souvent les sociétés veulent développer en TDD sans pour autant modifier les temps de développement, c’est une erreur.