TDD en pratique : Comment l'utiliser concrètement

Vous voulez améliorer la qualité du code de vos projets de développement de logiciels ? Le développement piloté par les tests (TDD) est une approche éprouvée qui peut vous aider à atteindre cet objectif. Dans ce guide étape par étape, je vous guiderai à travers le processus de mise en œuvre du TDD dans vos projets, de la création de tests de base au refactoring continu du code. Plongeons dans l'aventure !

Pourquoi je fais du TDD ?

Comme je l'ai mentionné dans mon dernier article, le TDD m'aide à garantir la qualité du code et à m'assurer que toutes les exigences d'une tâche sont remplies. Il m'a fallu beaucoup de temps avant de comprendre comment fonctionne le TDD, parce que je n'avais pas eu un bon exemple de la façon dont le processus fonctionne réellement. C'est pourquoi je souhaite le partager avec vous aujourd'hui.

Mon parcours dans le domaine du TDD a commencé avec la vidéo suivante, dans laquelle Robert C. Martin fait une démonstration du processus :

Robert C. Martin montre comment fonctionne le TDD (à partir de 43:48 min)

Créer des tests de base

Comme j'aime l'exemple d'Uncle Bob pour montrer le TDD lors du développement d'une pile, je le prendrai également comme exemple. Ma mise en œuvre se fera en C# en utilisant xUnit comme cadre de test. Si vous n'êtes pas familier avec xUnit, jetez un coup d'œil à leur documentation . Tout d'abord, vous devez vous assurer que vous disposez d'un environnement d'exécution. Cela signifie que vous avez un projet d'application et un projet de test qui fait référence au projet d'application. Si le test peut être exécuté, l'environnement d'exécution fonctionne. Pour créer un tel environnement, je crée une nouvelle solution avec une application console et je référence le projet d'application console au projet de unit test.

JetBrains Rider Project Explorer montrant le projet d'application console et le projet d’unit test qui contient une référence.
Connecter le projet de test unitaire à l'application console

La classe C# StackTest contient un test simple qui ne fait rien et qui sert simplement à vérifier que le programme d'exécution des tests peut être exécuté.

namespace TddDemo.UnitTests;

public class StackTest
{
    [Fact]
    public void Nothing()
    {
    }
}

Enfin, il vous suffit d'exécuter les tests et de voir s'ils fonctionnent correctement ou si vous constatez des problèmes.

Le programme de test JetBrains Rider affiche un seul test unitaire appelé "Nothing" qui a été exécuté avec succès.
Vérifiez dans le gestionnaire de test de votre IDE si le test a été exécuté avec succès

La deuxième étape de la mise en œuvre du TDD consiste à créer un test de base qui vérifie une fonctionnalité spécifique de votre programme. Ce test sert de modèle au code que vous allez écrire. En commençant par un test, vous comprenez clairement ce que vous voulez que votre code accomplisse. Mais par où commencer ? Il existe une règle simple pour cela :

Vous écrivez le test qui vous oblige à écrire le code que vous savez déjà vouloir écrire.

Robert C. Martin alias “Uncle Bob”

Nous devons donc commencer par quelque chose d'aussi simple que l'écriture d'un test qui devrait créer une stack.

    [Fact]
    public void CreateStack()
    {
        var myStack = new MyStack();
    }

Mise en œuvre d'un code minimal

Mais voilà que l'IDE lance une erreur, disant qu'il n'a pas pu résoudre le symbole "MyStack". C'est là que le cycle commence. Rappelez-vous le dernier article que j'ai écrit sur le TDD avec les trois phases "rouge", "verte" et "refactor". Maintenant, vous comprenez peut-être pourquoi la première phase est appelée "rouge" : Nous écrivons du code jusqu'à ce que nous trouvions une erreur. C'est à ce moment-là que nous devons la corriger.

L'objectif est d'écrire le code le plus simple possible qui réponde aux exigences du test. En vous concentrant sur la réussite du test, vous vous assurez que votre code fonctionne correctement dès le départ. Dans ce cas, il suffit de créer une nouvelle classe appelée "MyStack" dans le projet d'application.

JetBrains Rider avec une nouvelle classe appelée "MyStack" dans le projet "TddDemo" qui est notre application dans ce cas.
La création d'une classe résout le problème

Maintenant, lorsque vous revenez à votre cas de test, vous voyez que l'erreur a disparu. Si vous exécutez le test maintenant, il est vert. Notre phase "verte" est donc également terminée.

Refactorisation continue du code

Après avoir passé le test, l'étape suivante est le refactoring continu du code. Il s'agit d'améliorer la qualité de votre code sans en affecter la fonctionnalité. En révisant régulièrement votre code, vous pouvez éliminer toute dette technique et vous assurer que votre base de code reste propre et maintenable. Personnellement, je n'aime pas le nom "MyStack", je vais donc renommer la classe en "Stack". Après avoir renommé la classe, exécutez à nouveau le test pour voir s'il est toujours réussi.

Poursuivre le processus

L'exemple donné n'était que le début du processus TDD et est donc un peu basique. De plus, le test ne vérifie rien. Pour vous donner un meilleur aperçu, j'ajoute un peu plus de logique au code pour vous montrer comment poursuivre le processus. Mais terminons d'abord notre premier test. Dans ce cas, il est logique de vérifier si la stack nouvellement créée est vide. Ajoutons donc cette évaluation au test.

    [Fact]
    public void CreateStack()
    {
        var myStack = new Stack();
        Assert.True(myStack.isEmpty());
    }

L'IDE affiche à nouveau une erreur. Rappelez-vous : nous sommes à nouveau dans la phase "rouge" - corriger le code. La solution dans ce cas est d'ajouter la méthode. Je laisse l'IDE faire le gros du travail pour moi ici 😉 .

Utilisation de l'outil de suggestion JetBrains Rider pour ajouter la méthode manquante "isEmpty" à la classe "Stack".
Ajouter la méthode en utilisant les outils de l'IDE

Exécutez le test maintenant et il sera rouge. Cette étape est cruciale pour voir si notre test fonctionne vraiment ou s'il est toujours vert.

JetBrains Rider Unit Test Explorer montre une erreur dans le test.
L'exception "méthode non implémentée" était attendue - nous savons maintenant que notre test fonctionne.

Pour résoudre ce problème, vous devez écrire le code le plus simple possible pour réaliser le test. Dans ce cas, il suffit de renvoyer true lorsque la méthode est appelée. Et le test fonctionne comme un charme.

"Mais pourquoi devrais-je procéder de cette manière ?" demanderez-vous. Avec cette méthode, vous voyez que votre test passe et échoue et vous n'avez pratiquement pas besoin de temps pour le tester.

Il est maintenant temps de procéder à une refactorisation. Tout d'abord, je renommerais la méthode pour respecter les conventions de nommage (de "isEmpty" à "IsEmpty"). Testez à nouveau et renommez ensuite la méthode de test (de "CreateStack" en "NewStackIsEmpty").

Pour continuer le processus, je crée un test pour pousser quelque chose dans la stack. Je suis arrivé jusqu'ici avant que l'IDE n'affiche une erreur :

    [Fact]
    public void PushInStack()
    {
        var myStack = new Stack();
        myStack.Push(5);
    }

Là encore, la méthode n'existe pas. Ajoutons-la donc et exécutons le test pour voir ce qui se passe.

JetBrains Rider : code de l'implémentation de push et résultat du test en rouge montrant une exception "méthode non implémentée".
L'exception n'était pas attendue, et nous constatons donc une erreur

Je supprime l'exception NotImplementedException et le test devient vert - problème résolu(?). Dans le refactoring, j'ai renommé le paramètre "i" en "element". Cela fonctionne toujours, sans surprise jusqu'à présent.

Là encore, j'ai besoin d'une assertion. Donc, supposons que "myStack.IsEmpty()" renvoie false. Et le test redevient rouge. Nous devons maintenant résoudre le problème.

L'une des règles du développement piloté par les tests consiste à mobiliser le moins de cellules cérébrales possible, car vous en aurez besoin plus tard. N'en faites pas trop, trop vite.

Robert C. Martin alias “Uncle Bob”

Ainsi, pour respecter cette règle, créons une variable dans la classe appelée "isEmpty" qui sera initialisée à "false" et passera à "true" lorsque la méthode "Push" sera appelée.

public class Stack
{
    private bool _isEmpty = true;
    
    public bool IsEmpty()
    {
        return _isEmpty;
    }

    public void Push(int element)
    {
        _isEmpty = false;
    }
}

Lors du refactoring, je supprimerais l'initialisation dupliquée de "myStack" afin que le code de la classe de test ressemble à ceci :

public class StackTest
{
    private readonly Stack _myStack = new();

    [Fact]
    public void NewStackIsEmpty()
    {
        Assert.True(_myStack.IsEmpty());
    }

    [Fact]
    public void PushInStack()
    {
        _myStack.Push(5);
        Assert.False(_myStack.IsEmpty());
    }
}

Le processus se poursuivrait désormais toujours ainsi.

Défis, raisons et solutions

Cela semble étrange au premier abord. Pourquoi ces petits pas ? Pourquoi ne pas commencer par implémenter le code ? Si vous écriviez d'abord votre logique, vous obtiendriez probablement un code non testable et non modulaire. Cela réduirait la couverture de test possible. Les petits pas sont importants pour s'assurer que les tests fonctionnent réellement. Sinon, il pourrait arriver qu'un bug se produise même si le test est vert, ce qui conduirait à des tests peu fiables. C'est un gros problème qu'il faut absolument éviter.

Mais il y a des avantages : l'utilisation de cette technique réduit la quantité de travail mental dont vous avez besoin pour créer du code, car vous vous contentez de réaliser des tests. Ensuite, vous avez de nombreux moments de réussite. Chaque test vert est un succès en soi. Ne devenez pas accro 😉 Enfin, vous pouvez être certain que le code que vous avez créé avec TDD fonctionne comme défini dans les tests. Cela réduit le stress et la quantité de tests manuels nécessaires.

Plus vous gagnez en expérience dans le domaine du TDD, plus vous devenez rapide. Il est possible que vous décidiez de sauter certaines étapes de configuration, mais vous devriez d'abord l'essayer.

TDD dans différents langages de programmation

Le TDD est une approche indépendante du langage qui peut être appliquée dans presque tous les langages de programmation. Que vous travailliez avec JavaScript, Python, Java ou tout autre langage, le TDD peut être mis en œuvre efficacement. Dans cet article, j'ai montré un exemple avec C# et xUnit.

Si vous cherchez à intégrer le TDD dans votre processus de développement mais que vous ne savez pas par où commencer, je suis là pour vous aider. Je peux vous guider à travers le processus de mise en œuvre de TDD et vous aider à atteindre l'excellence en matière de qualité du code.

Laisser un commentaire