Débuter en Test Driven Development par la pratique

Les tests automatisés, parfois maladroitement appelés tests unitaires (parce que les tests unitaires font partie du concept plus large que sont les tests automatisés), sont une pratique en développement qui m’intéresse beaucoup. Il s’agit d’écrire du code qui inspecte le code de l’application, pour voir si ce dernier se comporte de la manière attendue. Bien qu’il faille évidemment écrire plus de code au total, le but est de rembourser le temps investi dans l’écriture de ce code de test en aidant à découvrir les failles qui ont pu s’introduire au cours des itérations du développement de l’application.

Plus précisément, le but de cet article est de vous montrer comment mettre en place une approche assez rigoureuse de cette pratique, qui est appelée Test Driven Development. Cette façon particulière de coder préconise d’écrire ses tests avant de commencer à écrire du code de production. Cela peut sembler un peu bizarre quand on en entends parler pour la première fois, parce qu’on va tester quelque chose qui n’existe pas encore. Je vais ici utiliser un cas pratique où j’ai mis le TDD en œuvre pour vous faire comprendre comment cela fonctionne et à quoi cela m’a servi.

Parlons du contexte

Avant de rentrer dans les détails techniques, je vais vous expliquer dans quel contexte j’ai décidé d’utiliser cette pratique: une activité que j’affectionne de plus en plus, est de participer aux défis d’intelligence artificielle proposés par le site CodinGame.com. Ces défis durent environ 10 jours et nous demandent d’écrire une intelligence artificielle pour un jeu que l’équipe ou la communauté CodinGame a développé pour l’occasion. Les IA de chaque joueurs s’affrontent dans ce jeu afin de classer les joueurs à travers différentes ligues. Ne vous braquez sur les termes techniques, ces défis sont ouverts à tous les niveaux de programmeurs (notamment grâce au système de ligue), et je m’y connaît moi-même très peu en intelligence artificielle.

Le jeu auquel mon IA a participé dans ce défi ressemble un peu aux échecs, mais avec une partie de gestion de ressources et de contrôle du territoire. Pour résumer: un joueur peut utiliser l’or pour créer des unités, qui captureront les cases sur lesquelles ils marchent, augmentant ainsi le revenu en or du joueur. Il y a un peu plus de règles, mais je vais les décortiquer dans les paragraphes qui suivent.

Un défi d’IA sur le site CodinGame

Entre la courte durée du «projet», et le fait que je n’allais pas allouer la majorité de mon temps à celle-ci, je n’ai pas songé à y mettre en place du Test Driven Development d’entrée de jeu. Mais après avoir visionné une conférence de Grégory Ribéron1, j’ai changé d’avis. Dans cette conférence, il explique les quatre parties distinctes de la programmation d’une IA.

Il y fait clairement la distinction entre la modélisation du contexte, et l’écriture du code de l’IA à proprement parler. Son argument était que l’on veut éviter d’avoir une IA qui prend de mauvaises décisions en raison de la façon dont on a modélisé les règles du jeu. Si l’on est pas au courant d’avoir fait une erreur dans la modélisation de ces règles (le contexte), on pourra essayer d’optimiser son IA avec tout son cœur et son âme, mais celle-ci continuera de prendre des décisions sous-optimales. Ce fut l’argument décisif pour moi.

Avec quelle technologie?

Comme CodinGame nous laisse choisir entre une grande variété de langages dans leur IDE en ligne, j’ai décidé d’utiliser Javascript, parce que je le connais suffisamment pour écrire du code de manière fluide. En outre, j’ai choisi d’utiliser le framework de tests automatisés Jest.

Qu’est-ce qu’un framework de tests automatisés, et pourquoi en ai-je eu besoin? Fondamentalement, j’aurais pu écrire mes tests en javascript natif. Mais cela est assez pénible à faire, et les mécanismes de base des tests automatisés sont si courants que les développeurs ont construit des outils pour accélérer l’ensemble du processus.

En fait, Jest est assez facile à installer, parce que c’est tout simplement un module Node.Js, et la documentation officielle de Jest2 est limpide. Si vous n’êtes pas familier avec ce framework ou Node.Js n’ayez pas peur, il suffit de suivre les étapes écrites dans leur guide « Getting Started ». Je vais vous en faire une démonstration immédiatement.

Mon premier scénario

En raison de la manière dont le Test Driven Development (TDD) fonctionne, j’ai du faire une inspection sommaire des règles avant d’écrire mes premières lignes de code. En effet, je dois faire passer un test pour m’assurer que mon programme est conforme à ces règles. J’ai donc commencé par m’intéresser au fonctionnement du mouvement des unités. Voici ce que les règles disent:

La carte est une grille de taille 12×12, où le coin supérieur gauche est la cellule (0,0). La carte est générée au hasard au début de chaque partie.

Tout cela est très compréhensible pour un humain, mais un ordinateur a besoin d’instructions beaucoup plus spécifiques. Donc, ma première décision a été d’écrire un test qui permet de vérifier qu’une unité ne va pas essayer de se déplacer en dehors de la carte.

Voici comment Robert C. Martin (alias Oncle Bob ), un évangéliste du TDD, a formulé les premières étapes du processus qu’il appelle trois lois du Test Driven Development3 :

1. Vous devez écrire un test défaillant avant d’écrire un code de production.
2. Vous ne devez écrire que le minimum nécessaire pour que le test échoue, ou ne compile pas.

Robert ‘Oncle Bob’ C. Martin – Les trois lois du TDD (traduis de l’anglais par moi-même)

Je garde la règle numéro 3 de côté pour l’instant, mais si j’applique ces deux premières règles à la lettre, mon premier test (et même mes premières lignes de code, parce que je dois écrire des tests avant tout code de production), ressemble à ça:

const Unit = require('./Unit')

test( 'Une unité ne peut pas se déplacer sur une autre unité', ()=> {
  let unit = new Unit()
  expect(unit.canMove()).toBe(false)
})

En effet, ce code a peu d’intérêt en soi, il semble juste que mon unité ne sera jamais en mesure de se déplacer. Cependant, cela suffit pour avoir un test qui échoue, et lorsque j’exécute mes tests avec la commande npm run test, voici ce que Jest me répond en retour:

 FAIL ./Unit.test.js
 Une unité ne peut pas se déplacer sur une autre unité (2ms)
 Une unité ne peut pas se déplacer sur une autre unité
 TypeError: unit.canMove n'est pas une fonction

Ce qui est logique, parce que je n’ai pas implémenté la méthode canMove() dans ma classe de Unit! Euh… oui en effet, j’ai une classe Unit. Parce que je n’ai pas commencé le TDD dès le début du projet, donc j’ai déjà un peu de code existant. Dans un scénario 100% TDD, mon test aurait échoué dès la première ligne, donc, par la deuxième loi du TDD, j’aurais dû écrire seulement la première ligne.

Bref, penchons-nous sur la règle numéro 3, qui dit:

3. Vous ne devez écrire que le strict minimum de code de production pour faire passer le test actuellement défaillant.

Robert ‘Oncle Bob’ C. Martin – Les trois lois du TDD (traduit de l’anglais par moi-même)

Je suppose que le code minimal à écrire serait assez simple, voici ce que je pense de:

class Unit {
    canMove()
      return false;
    }
}

Et le test passe, évidemment. C’est tout? J’ai une fonction qui valide un test, mais son résultat est toujours false, ce qui n’est pas très utile à ce stade. Eh bien, ce test ne vérifie qu’un seul scénario (qui n’est pas très bien défini maintenant, je dois l’admettre). Je vais développer d’autres scénarios dans les paragraphes suivants, et ils seront traités par d’autres tests. Cela m’aidera à identifier plus précisément ce qui ne va pas avec mon code de production.

Plus de tests !

Comme je n’ai plus de test défaillant, la troisième loi du TDD m’interdit d’écrire plus de code de production. Je dois donc écrire un nouveau test. Celui-ci concerna le scénario où notre unité peut effectivement se déplacer. Examinons la classe Tile que j’ai écrite avant d’adopter le TDD :

class Tile {
    constructor(x, y, type, data = null) {
        this.x = x
        this.y = y
        this.type = type
        this.data = data
    }

    // ...

}

Le paramètre type se réfère à un personnage qui correspond aux entrées que mon programme lit à partir de l’API de Codingame, dont voici la documentation :

Une cellule du plateau peut être soit:

– inaccessible (#) : aucune unité ne peut s’y déplacer.

– neutre (.): n’appartient à aucun joueur.

– capturée (O ou X): appartient à un joueur.

– inactive (o ou x): appartient à un joueur mais ne génère pas d’or.

Par définition, une tuile neutre ne peut contenir aucune unité, sinon il s’agirait d’une tuile contrôlée par un joueur. Alors je suppose que je peux tester que mes unités puissent toujours se déplacer sur une tuile neutre. Ce devrait être un test assez facile :

const Tile = require('./Tile')

test ( 'Une unité peut se déplacer sur une tuile neutre', ()
    let unit = new Unit(1)
    let tile = new Tile(0, 0, '.')
    expect(unit.canMove(tile)).toBe(true)
})

C’est très simple : j’instancie une nouvelle Tile, à laquelle laquelle je passe des coordonnées et un paramètre type. Ensuite, je vérifie ma méthode, et comme prévu, le nouveau test échoue (parce que ma méthode canMove retourne toujours false). Il est donc temps de changer mon code de production, puisque j’ai un test défaillant à résoudre. Je vais coder ça rapidement:

canMove( tile ) {
  if ( tile.type === '.' ) {
    return true
  }
  return false
}

Ok, il est temps de relancer mes tests. Mon nouveau test passe, mais mon premier test échoue!

    TypeError: Impossible de lire la propriété «type» de undefined

Evidemment, puisque je n’ai pas instancié une case dans ce test, et que mon code de production doit recevoir un paramètre représentant cette case. Je vais donc le modifier, mais j’ai besoin de le faire avec un brin de réflexion, parce que mon test est nommé «Une unité ne peut pas se déplacer en dehors de la carte». Donc, ce ne sera pas n’importe quelle case que je vais instancier, mais une case en dehors des coordonnées de la carte. Eh bien, puisqu’il n’y a quatre directions dans lesquelles je peux sortir de la carte, je pourrais aussi bien l’écrire avec quatre cases différentes, dans le même test.

test ( 'Une unité ne peut pas se déplacer en dehors de la carte', ()'
  let unit = new Unit()

  let tileNorth = new Tile(0, -1, '.')
  let tileWest = new Tile(-1, 0, '.')
  let tileSouth = new Tile(0, 13, '.')
  let tileEast = new Tile(13, 0, '.')

  expect(unit.canMove(tileNorth)).toBe(false)
  expect(unit.canMove(tileWest)).toBe(false)
  expect(unit.canMove(tileSouth)).toBe(false)
  expect(unit.canMove(tileEast)).toBe(false)
})

Ce qui échoue encore, parce que je n’ai toujours pas mis à jour mon code de production en conséquence. Le voilà:

canMove( tile ) {
  if ( tile.type === '.' &&
  tile.x <= 12 && tile.x >= 0 &&
  tile.y <= 12 && tile.y >= 0) {
    return true
  }
  return false
}

Cela fait beaucoup de code pour tester une fonction aussi simple, je l’admets. Mais je dois être sûr que je ne laisse aucun cas de côté. En fait, pour m’aider dans cette tâche, chaque framework de tests automatisés que je connais de met en œuvre une fonctionnalité appelée couverture de code.

Note: J’ai instancié mes cases avec le paramètre type défini à ‘.’ (qui représente une case neutre). En effet, je ne veux pas que mon test échoue en raison d’une mauvaise valeur pour ce paramètre, mais seulement avec des coordonnées en dehors de la carte.

Couverture de test

Cette fonctionnalité bien pratique est fournie par Jest si je lui demande gentiment avec l’option --coverage. Mais je ne veux pas vérifier la couverture chaque fois que je lance mes tests, parce que cela a un coût qui dégrade les performances des tests. Je crée donc un autre script que j’ajoute dans mon package.json :

"scripts": {
   "test": "jest",
   "test-couverture": "jest --coverage"
},

Maintenant, si j’exécute la commande npm run test-couverture dans ma console, la sortie affiche une belle table qui me montre quel pourcentage de mon code de production j’ai couvert avec mes tests. Mais ce n’est pas tout, j’ai aussi avoir un rapport plus détaillé qui est généré dans le sous-dossier coverage/lcov-reportsous forme de fichiers HTML. Je peux les ouvrir avec mon navigateur web et il me montre les lignes exactes de code qui sont couvertes par mes tests, et celles qui ne le sont pas.

Ouvrons le rapport pour mon fichier Unit.js:

Comme vous pouvez le voir, les marques vertes indiquent que mes lignes sont couvertes. Mais il y a quelque chose qui me tracasse: vous pouvez voir que la ligne 21 a été parcourue 5 fois durant mes tests (le petit «5x» à côté de lui). Comme Jest est un outil automatique, il a des limites dans la compréhension du code que les humains ont écrit. Ici, ça ne fait pas de différence pour lui si mon test échoue parce que la case n’est pas du bon type, ou qu’elle est trop loin à l’est, à l’ouest, au nord ou au sud… C’est la même ligne de code pour lui.

Cela donne deux informations. Tout d’abord, qu’un rapport de couverture de code, bien qu’utile, n’est en aucun cas une assurance que l’ensemble de votre code soit testé dans tous les cas qui puissent exister. Et ainsi, les erreurs peuvent toujours exister dans des parties que vous ne soupçonnerez pas. Et deuxièmement, que si je veux profiter le plus de mes tests, je vais devoir écrire mon code de production avec moins de concision, afin qu’il puisse être analysé avec plus d’efficacité par mon framework de tests automatisés. Essayons :

canMove( tile ) {
  if ( tile.x <= 12 ) {
    return false;
  } else if ( tile.x >= 0 ) {
    return false;
  }

  if ( tile.y <= 12 ) {
    return false;
  } else if ( tile.y >= 0 ) {
    return false;
  }

  if ( tile.type === '.' ) {
    return true
  }
  return false
}

Je l’avoue, ce code est moche, mais … Oups! Les tests échouent. Voyons ce qui s’est passé… Ah! J’ai modifié la logique de la méthode, qui retourne false si l’une des conditions vérifiant les coordonnées observe une valeur incorrecte; mais je n’ai pas inversé les signes de comparaison lors de la refactorisation! C’est un des grands avantages du TDD: je peux refactoriser mon code à volonté, et je serai averti chaque fois que je casse quelque chose dans le processus. Le grand avantage est que l’information est immédiate, et parce que je teste fréquemment, je sais que l’erreur doit avoir été créée dans l’intervalle de quelques minutes entre mon dernier test et le moment présent. Et c’est aussi pourquoi il est important de toujours faire passer tous ses tests avant d’écrire plus de code de production: cela réduit les pistes des éventuelles erreurs que l’on a introduit.

Après correction, voici ce à quoi ressemble ma couverture de code:

Beaucoup mieux, nous pouvons voir que chacun de mes return pour une valeur de coordonnées incorrectes est testé au moins une fois. On s’apperçoit également que le dernier return, le comportement par défaut, n’est pas testé. Je peux le le faire facilement, en testant le cas où la case est une « case vide », représentée par un « # ». Je crois que vous avez compris comment tout cela fonctionne maintenant, et je ne vais pas détailler tous les tests de ce projet.

Note : Dans ma note précédente, j’avais peur que les tests avec des cases d’un autre type que neutre (le ‘.’) puissent faire passer le test, mais pour une mauvaise raison. De la manière dont mon code de production est écrit actuellement, les coordonnées sont vérifiées avant le type de tuile, ce qui fait que cela n’a plus d’importance quel type de case j’instancie dans mon test. Mais je vais le garder comme tel, juste pour être parfaitement clair. (En outre, j’aurais pu résoudre ce problème en prenant l’avantage de l’évaluation paresseuse de javascript …)

Allons plus loin

En lisant la suite des règles, on se rend compte que les unités ont des règles spéciales, qui leur permettent de détruire une autre unité en fonction de leurs niveaux respectifs:

Les unités de l’armée ne peuvent détruire que des unités de niveau inférieur, à l’exception des unités de niveau 3 qui peuvent détruire n’importe quelle unité.

Je commence donc par écrire le test pour mon unité de niveau 1 :

test ( 'Une unité de niveau 1 ne peut pas se déplacer là où il y a une unité de son niveau ou au-dessus', ()
  let unitLvl1 = new Unit(1)
  let tileWithUnitLvl1 = new Tile(0,0, 'O', new Unit(1))
  let tileWithUnitLvl2 = new Tile(0, 1, 'O', new Unit(2))
  let tileWithUnitLvl3 = new Tile(1, 1, 'O', new Unit(3))

  expect(unitLvl1.canMove(tileWithUnitLvl1)).toBe(false)
  expect(unitLvl1.canMove(tileWithUnitLvl2)).toBe(false)
  expect(unitLvl1.canMove(tileWithUnitLvl3)).toBe(false)
})

Vous le devinez aisément, le paramètre passé au constructeur de la classe Unit est le niveau de l’unité qui est instanciée. Eventuellement, je pourrais totalement copier / coller ce code et changer les valeurs pour le niveau 2 et le niveau 3, mais comme avec chaque copié / collé de code, il y a probablement mieux à faire… Et pour cause, Jest nous offre de nouveau une fonctionnalité qui va nous aider (et qui est courante dans d’autres frameworks de tests automatisés). La syntaxe peut varier pour chaque framework, mais le concept reste le même, c’est ce qu’on appelle des tests paramétrés. Voyons à quoi cela ressemble.

test.each
  [1, false, false, false],
  [2, true, false, false],
  [3, true, true, true]
])(
  «Une unité de niveau %i ne peut pas se déplacer là où il y a une unité de son niveau ou au-dessus»,
  (level, expectLvl1,expectLvl2, expectLvl3) => {
    let unitLvl1 = new Unit(level)
    let tileWithUnitLvl1 = new Tile(0,0, 'O', new Unit(1))
    let tileWithUnitLvl2 = new Tile(0, 1, 'O', new Unit(2))
    let tileWithUnitLvl3 = new Tile(1, 1, 'O', new Unit(3))

    expect(unitLvl1.canMove(tileWithUnitLvl1)).toBe(expectLvl1)
    expect(unitLvl1.canMove(tileWithUnitLvl2)).toBe(expectLvl2)
    expect(unitLvl1.canMove(tileWithUnitLvl3)).toBe(expectLvl3)
  }
)

Il y a pas mal de nouveautés ici. Je vais les passer en revue, mais ce que vous avez sûrement compris, c’est que c’est le même principe qu’une fonction paramétrée.

Tout d’abord, le tableau de tableaux transmis à la fonction each contient les valeurs de ces paramètres. Chacun des tableaux intérieurs correspond à une différente exécution du test. Comme j’ai trois tableaux intérieurs, mon test sera exécuté trois fois. Les valeurs que les paramètres prendront sont les valeurs à l’intérieur de ces tableaux. Quels sont ces paramètres?

Comme vous pouvez le voir, après le nom du test, j’ai nommé ces paramètres. L’ordre est important ici, car la première valeur dans chacun de mes tableaux intérieurs est passée en tant que premier paramètre, le second en second, et ainsi de suite …

Mais il semble que j’ai sauté une étape. Vous l’avez sûrement remarqué, mais le nom du test contient une expression spéciale, qui est %i. Cette expression sera remplacée par un paramètre, et i spécifie que celui-ci sera formaté comme un entier. Ainsi, chaque fois que le test est exécuté, il changera de nom en fonction du premier paramètre qui lui est passé. J’aurais tout à fait pu laisser le même nom pour chaque exécution de ce test, mais il aurait été plus difficile de deviner les conditions dans lesquelles ce test échoue ou passe.

Ensuite, dans le corps de la fonction de test, vous pouvez voir que chaque paramètre est remplacé parles valeurs que j’ai transmises au test. Évidemment, le premier paramètre est utilisé pour modifier le niveau de l’unité instanciée. Enfin, les trois suivants sont les valeurs que j’attends dans mes assertions.

Un grand avantage de cette syntaxe, c’est que je peux facilement vérifier si mon test est pertinent ou non. Dans les paramètres, je peux rapidement vérifier que mes calculs sont corrects. Et dans le code du test, je n’ai qu’à inspecter que ma logique est juste. En parlant de la facilité de lecture …

Note: Portez une attention particulière à la syntaxe utilisée. La méthode each se termine après le tableau de paramètres, mais comme celle-ci retourne une fonction, j’ai dû ouvrir une nouvelle parenthèse directement après la fermeture de celles de la méthode each, afin de passer les paramètres à cette nouvelle fonction de test créée dynamiquement.

Une syntaxe plus claire pour les tests

Lorsqu’on commence à écrire des tests automatisés, d’autant plus en Test Driven Development, il y a une forte probabilité que le but soit de produire du code qui sera réutilisé et modifié à l’avenir (ou peut-être que vous êtes juste des testeurs compulsifs comme moi). Dans tous les cas, vous pouvez être sûr que les tests seront également modifiés en conséquence. Parce que certaines fonctions deviendront obsolètes, d’autres auront besoin de tests plus spécifiques, voire même de changer le type de paramètres qu’elles attendent… Pour cela, il serait bienvenu d’être en mesure de comprendre ce que l’on a testé assez rapidement, sinon tout le processus risque d’être usant. Cela est d’autant plus important lorsque le code est réutilisé et modifié par d’autres développeurs!

Maintenant que vous avez été averti, je voudrais vous proposer une façon assez commune de structurer ces tests en trois étapes:

  1. Le contexte : Lorsqu’on écrit un test, on crée un contexte spécifique dans lequel le programme est exécuté. On doit être en contrôle total de ce contexte, et j’ai du mal à souligner avec suffisamment d’emphase l’importance de ce principe. Parce que ces tests réussissent ou échouent, il faut connaître les détails qui ont mené à cet échec ou cette réussite. sinon on ne sera pas en mesure de déduire les raisons pour lesquelles le test a échoué (à noter que le test peut également passer, mais avec un bug inaperçu). Il est courant de nommer cette étape Given.
  2. L’action : C’est ici que l’on teste le comportement de la partie du programme qui nous intéresse. On nommera cette étape When.
  3. L’assertion : C’est à ce moment que l’on compare les valeurs obtenues à l’étape précédente avec les valeurs que l’on attends. C’est l’étape Then.

Voici à quoi cela ressemble:

(
  «Une unité de niveau %i ne peut pas se déplacer là où il y a une unité de son niveau ou au-dessus»,
  (level, expectLvl1,expectLvl2, expectLvl3) => {
    // Given
    let unitLvl1 = new Unit(level)
    let tileWithUnitLvl1 = new Tile(0,0, 'O', new Unit(1))
    let tileWithUnitLvl2 = new Tile(0, 1, 'O', new Unit(2))
    let tileWithUnitLvl3 = new Tile(1, 1, 'O', new Unit(3))

    // When
    let canMoveOnLvl1 = unitLvl1.canMove(tileWithUnitLvl1)
    let canMoveOnLvl2 = unitLvl1.canMove(tileWithUnitLvl2)
    let canMoveOnLvl3 = unitLvl1.canMove(tileWithUnitLvl3)

    // Then
    expect(canMoveOnLvl1).toBe(expectLvl1)
    expect(canMoveOnLvl2).toBe(expectLvl2)
    expect(canMoveOnLvl3).toBe(expectLvl3)
  }
)

Remarquez comment j’ai changé mon code pour faire une distinction entre l’action et les assertions (que j’avais précédemment écrit sur la même ligne pour la concision). Bien que mon code de test soit plus verbeux, il est également plus facile à comprendre avec un coup d’oeil rapide.

On peut utiliser cette structure pour planifier le test qu’on va écrire en l’écrivant en français d’abord (ou toute autre langue que vous parlez). Par exemple, j’aurais pu (et j’aurais dû) écrire le commentaire suivant au début du processus:

/* Étant donné que j'ai une unité de certain niveau, et trois cases sur lesquelles se dressent respectivement:
    - une unité de niveau 1
    - une unité de niveau 2
    - une unité de niveau 3
*/

// Quand j'essaie de déplacer mon unité sur chacune de ces cases.

// Alors je m'attends à ce que mon unité ne puisse se déplacer que sur les cases avec une unité d'un niveau inférieur,
sauf si mon unité est de niveau 3, auquel cas elle devrait être en mesure de se déplacer sur chaque case .

C’est une bonne pratique parce que l’écriture de test automatisé ne doit pas être un processus automatique en soi. En fait, si l’on a soigneusement pensé à ses tests, on a probablement déjà fait 80% de la réflexion sur la manière d’écrire le code de production également. L’objectif, comme les valeurs en entrée et en sortie du système en cours de test sera déjà prévu, on aura plus qu’à définir les détails d’implémentation .

Pour conclure

Eh bien, j’espère que la notion de Test Driven Development fait est un peu plus claire pour vous maintenant, et que vous êtes désireux de commencer à le mettre progressivement en œuvre dans vos propres projets. En fait, c’est quelque chose que les développeurs ne réalisent peut-être pas immédiatement, mais ce n’est pas parce qu’on a décidé de faire un peu de TDD, que chaque petit bout de votre application doit être couvert par des tests!

Je l’admets, juger quelles parties bénéficieraient plus de tests automatisés que d’autres n’est pas évident quand on est débutant. Mais dans le doute, vous devriez vraiment essayer d’écrire juste quelques tests. Ne poussez pas trop loin au début! Le TDD est censé être un outil pour gagner du temps, donc si vous bloquez sur la rédaction de vos tests, prenez du recul, analysez si vous avez vraiment besoin de tel ou tel test, et décidez si vous ne pouvez pas juste l’écrire plus tard.

Dans l’ensemble, c’est autant une compétence que l’implémentation de fonctionnalités, et vous vous débrouillerez de mieux en mieux si vous suivez une pratique régulière. Alors commencez maintenant, essayez de pousser un peu plus loin à chaque fois, et bientôt vous construirez des suites de tests robustes vous aidant à localiser les bugs dans la minute où ils sont introduits dans vos applications. Vous avez ma parole !

Ressources

  1. Grégory Ribéron – De bronze à légendaire, commentaire réussir vos AIs ?
  2. Jest Documentation officielle
  3. Le blog Clean Code – Les cycles de TDD