Teste ton putain de code

Par Didier Sampaolo, CTO @ lvlup.

Tu veux siroter des cocktails sur une plage, pendant que ton code travaille sans toi ? Si tu veux éviter les coups de fil toutes les 5 minutes, chaque fois que quelque chose déraille, il n'y a qu'une solution : teste ton putain de code.

Qu'on soit bien d'accord, je ne vais volontairement pas te noyer sous un flot de détails techniques à propos des tests. L'idée de ce post est de t'expliquer pourquoi c'est important, et en quoi c'est sympa.

Mettons-nous en situation

Scénario catastrophe : On te remonte un petit bug dans du code que tu as écrit. Pas dramatique, juste un edge case mal géré. Une fois les mains dans le code (qui date de 6 mois), tu te rappelles du jour où tu l'as écrit. C'était un peu crade, mais tu n'avais pas le temps de faire mieux, et ça marchait, donc c'est passé en prod tel quel. Aucun jugement ici, ça arrive à tout le monde. Tu corriges ton petit bug, et tu pousses ton fix en prod. Cinq minutes plus tard, un des mecs du marketing (en général, celui qui te gonfle le plus) arrive tout affolé pour savoir ce qu'il se passe, en beuglant "ÇA MARCHE PAS". T'as pété un truc. Grosse régression. Changement d'ambiance : d'un coup, c'est le feu, tout le monde gueule, t'as la pression et tu dois réparer ta connerie ET le bug qu'on t'avait remonté ce matin, le plus vite possible, sous pression.

Ça aussi, ça arrive à tout le monde. Enfin, plus à moi, ça m'a gonflé et j'ai fini par le comprendre : le code, ça se teste.

Même scénario, une fois tes tests en place : On te remonte un souci : un edge case qui n'est pas géré. Tu ajoutes le test qui le couvre, puis tu répares le bug. Tu fais tourner tes tests. Oups, ton fix casse autre chose. Tu reprends. Au deuxième essai, ça marche. Tu rajoutes deux-trois assertions pour le sport, tu pousses en prod. Café ?

Comme le dit Mehdi Zed, "Et si j’entends encore quelqu’un dire que tester, ça fait perdre du temps, je lui fais avaler son clavier". D'ailleurs, tiens, va lire sa prose sur les compétences clefs pour les développeurs.

Le nez dans la QA

La QA, pour Quality Assurance, ça devrait faire partie de ta culture. Ton boulot n'est pas d'écrire du code, mais de résoudre des problèmes, et le code est un outil. Tu te dois d'affuter ton outil, et chaque action que tu mènes et qui va dans ce sens est une action positive.

J'entends souvent dire qu'il est très difficile de faire accepter au client qu'il va devoir payer pour le temps de rédaction des tests. C'est un faux problème : tu n'as pas à lui demander son avis, c'est une histoire entre toi et ton code. Évidemment qu'il va devoir payer, comme il doit payer pour les licences de tes logiciels, ton électricité et ton café.

Je n'ai que deux bras et j'ai envie que mes projets avancent vite, donc j'ai pris la bonne habitude d'acheter du code. Ça m'aide à mettre ce genre de coûts en perspective : est-ce que je préfère payer mon code 30 % moins cher, ou avoir la certitude qu'il fonctionne comme prévu ?

Par quoi je commence ?

Imaginons que tu doives ajouter une feature à un site. Commence par rédiger (à l'arrache) un scénario d'utilisation : "Je suis utilisateur. Quand je valide un paiement, mon compte prépayé est débité du montant, et j'ai une nouvelle facture disponible".

La première étape va être d'écrire un test, qu'on nommera par exemple "un_utilisateur_peut_valider_un_paiement". Dans ce test, on appelera la fonction $user->validatePayment($payment), et on vérifiera si la date de validation du paiement est bien passée de null à une date courante. Ça s'appelle faire une assertion : on s'attend à ce comportement. Un test "qui passe", c'est un test pour lequel toutes les assertions ont été vérifiées.

À partir de notre exemple fictif, voici quels tests j'écrirais pour commencer :

  • un utilisateur peut valider un paiement
  • la validation d'un paiement pioche dans le crédit d'un utilisateur
  • la validation d'un paiement entraîne la création d'une facture

Évidemment, puisqu'on n'a pas encore écrit de code, ces tests ne passeront pas. Je vais maintenant écrire le minimum de code possible. Bravo, on vient de faire du TDD (Test Driven Development) : ce sont les tests qui ont motivé la rédaction du code.

Et c'est là que ça devient marrant. Une fois le premier code jeté en place, il est maintenant temps de le passer au grill. Réfléchissons aux problèmes futurs (aux erreurs courantes qui vont se présenter en utilisation normale de l'appli) : un utilisateur peut ne pas avoir assez de crédit, le paiement peut avoir déjà été validé, etc. Chacun de ces petits scénarii d'erreur fera l'objet d'un nouveau test, ou d'une assertion dans un test existant.

Ensuite, deuxième passe : comment quelqu'un de mal intentionné pourrait-il me la faire à l'envers. Si le montant du paiement est négatif, est-ce que le crédit est quand même retiré à l'utilisateur (ou ajouté ?) Est-ce que la méthode/route peut etre appelée par quelqu'un qui n'est pas connecté ? Est-ce qu'on peut valider un paiement pour un autre utilisateur ? etc.

On revient à notre scénario catastrophe : maintenant que tu as la méthode, tu vas pouvoir appeler Antoine (prénom choisi presque au hasard) et le faire participer à la rédaction des scénarii. Au début, il sera frileux, mais une fois qu'il aura pris le coup, il sera utile pour t'aider à repérer les edge cases avant qu'ils n'arrivent en prod, et à écrire les tests qui vont avec.

Quand un souci arrivera, il saura d'avance que c'est parce qu'un des scénarii n'était pas assez complet.

Tu verras qu'au bout de quelques temps, ça va devenir un jeu : chacun voudra écrire le scénario le plus complet, et même si tu bosses seul, tu vas te faire un noeud au cerveau pour être certain de n'avoir laissé aucune faille. Là où tu pouvais parfois te dire "bof, ça suffira", tu passeras deux heures de plus pour essayer d'être vraiment exhaustif. Et la qualité de ton code s'en ressentira. Tu passeras moins de temps en debug (c'est une promesse) et plus de temps sur de nouvelles features. Ça débloquera aussi du temps pour nettoyer et maintenir ton code, via le refactoring.

Autre avantage : le refactoring

Le fait d'avoir une suite de tests solide répond aussi à une question qu'on me pose souvent : "je ne sais pas si je dois mettre le code dans un controlleur, dans un modèle, ou créer un service à part". Ma réponse sera simple : on s'en fout. Fais passer tes tests, et on refactorisera ton code plus tard si ça s'avère nécessaire. Avec des tests au carré, on n'a pas peur de mettre les mains dans son code pour le faire muter en fonction de la demande, puisque son bon fonctionnement est verrouillé. Chaque erreur de refactoring fera péter un test.

Le refactoring est un excellement moyen de se coller à écrire des tests quand on n'en a pas l'habitude. La prochaine fois que tu dois t'en taper une session, réfléchis à tes scénarios existants, écris tes tests (qui, cette fois, devront passer tout de suite), puis modifie ton code, en faisant tourner tes tests le plus souvent possible.

Si tu t'y mets aujourd'hui, je te promets que dans six mois, tu ne te poseras plus de questions, et que tu testeras systématiquement tout le code que tu écris.

J'apporte ma pierre à l'édifice
T'en veux encore ?