Les Tests ne Consistent pas Seulement à Écrire des Tests

- Read in English

Note

Je ne suis pas un expert en tests. Je partage simplement mes réflexions sur les tests en tant que développeur, dans un article que j'aurais apprécié lorsque j'ai commencé à coder.

J'ai commencé à coder il y a six ans. Depuis, j'ai entendu beaucoup parler des tests, mais j'ai vu peu d'exemples réels et pratiques.

Dans de nombreux articles que j'ai lus, les tests ressemblent à ça :

js
it('should return 2 when 1 + 1', () => {
  expect(1 + 1).toBe(2)
})

C'est loin des scénarios du monde réel et cela n'aide pas à comprendre la véritable valeur des tests.

Je dois admettre que, quand je ne comprends pas quelque chose, j'évite de l'utiliser. Néanmoins, depuis ma première rencontre avec le concept de tests, cela a persisté dans mon esprit, me poussant à rassembler des connaissances à ce sujet.

Un Long Voyage

J'ai d'abord entendu parler des tests lorsque j'ai appris Vue 2 en 2019. Dans la section des outils, sous la page "Single File Component", j'ai trouvé une section dédiée aux "Tests".

Je pense que j'ai lu le début de la page mais je n'ai jamais été plus loin. Il y avait trop de nouveaux concepts à saisir, trop d'outils à apprendre, et trop de choix à faire. À ce moment-là, j'ai compris que les tests front-end sont de loin la partie la plus difficile des tests.

En 2020, avec quelques amis, nous avons développé une plateforme de tutorat. Le serveur API a été construit avec Feathers.js 4. À ce moment-là, j'avais encore du mal avec les tests. Je me souviens avoir cherché la page des tests dans la documentation de Feathers.js et même l'avoir lue. Malgré le fait de ne pas bien la comprendre, j'ai écrit de nombreux tests. C'était une vraie lutte, car la distinction entre les tests unitaires et les tests d'intégration m'était floue, et les exécuter dans un CI était très difficile. J'utilisais MongoDB et Azure CI. En y repensant, je pense que je voulais souffrir. Je me souviens aussi avoir essayé d'atteindre 100 % de couverture. J'étais tellement naïf.

ts
it('should patch', async () => {
  expect.assertions(5)

  const patchedResult: Department = await app
    .service(serviceName)
    .patch(result._id, anotherDepartment)

  expect(patchedResult).toBeDefined()
  expect(patchedResult).toHaveProperty('_id')
  expect(patchedResult).toHaveProperty(
    'name',
    anotherDepartment.name.toLowerCase()
  )
  expect(patchedResult).toHaveProperty('createdAt')
  expect(patchedResult).toHaveProperty('updatedAt')
})

it('should delete', async () => {
  expect.assertions(5)

  const deleteResult: Department = await app
    .service(serviceName)
    .remove(result._id)

  expect(deleteResult).toBeDefined()
  expect(deleteResult).toHaveProperty('_id')
  expect(deleteResult).toHaveProperty('name', result.name.toLowerCase())
  expect(deleteResult).toHaveProperty('createdAt')
  expect(deleteResult).toHaveProperty('updatedAt')
})

Mais plus j'ajoutais de fonctionnalités à la plateforme, plus je réalisais la valeur des tests. Ils peuvent garantir qu'une nouvelle fonctionnalité ne casse pas une fonctionnalité existante sans l'ennui de tester manuellement tout. Cela était particulièrement évident parce que Feathers.js a de nombreuses parties complexes et des services qui peuvent facilement se casser lorsqu'une partie est modifiée.

ts
export default {
  before: {
    all: [],
    find: [],
    get: [],
    create: [checkData(checkDataOptions)],
    update: [disallow()],
    patch: [checkData(checkDataOptions)],
    remove: [],
  },

  after: {
    all: [],
    find: [iff(isProvider('external'), pickResult())],
    get: [iff(isProvider('external'), pickResult())],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
}

La complexité de l'application a crû de manière exponentielle, fonctionnalité après fonctionnalité. Cela était plus lié à l'architecture de Feathers.js, mais combiné avec les défis liés à l'écriture de tests efficaces, c'était un cauchemar à maintenir. Chaque changement faisait échouer tant de tests que j'étais incertain du bon fonctionnement de l'application.

À partir de ce moment-là, j'ai décidé que la capacité à tester l'application était un critère pour choisir une nouvelle pile. Et ce sera un long voyage, car en 2024, j'apprends encore beaucoup mais j'écris plus de tests que jamais.

Début 2021, Strapi gagnait en popularité, et je l'ai envisagé comme un remplacement potentiel pour Feathers.js, toujours pour construire la plateforme de tutorat. J'étais heureux de voir que Strapi avait une page dédiée aux tests dans sa documentation.

Alors, je me suis lancé et j'ai créé un nouveau projet avec Strapi pour commencer et voir si les tests étaient efficaces.

Malheureusement, j'ai immédiatement rencontré un problème qui n'a jamais été résolu. Incapable d'exécuter les tests, j'ai abandonné et la plateforme de tutorat n'a jamais été migrée vers Strapi, ni déployée en production. J'ai beaucoup appris de ce projet annexe. Souvent, le voyage est plus important que la destination.

Capture d'écran de ce problème 9376
Capture d'écran de ce problème 9376

Alors, récapitulons :

  • Je ne connais que JavaScript.
  • Feathers.js a une architecture complexe et une documentation médiocre sur les tests.
  • Strapi a une bonne documentation sur les tests, mais je n'ai pas pu exécuter les tests.
  • Je dois trouver un nouveau projet annexe pour apprendre les tests.

Quelques mois plus tard, j'ai entendu parler d'AdonisJS. Je ne me souviens pas comment, mais à ce moment-là, la version 5 était en bêta. C'était une bouffée d'air frais car la documentation semblait très bonne, et le framework était riche en fonctionnalités. Plein de fonctionnalités, oui, mais la partie tests n'était pas encore là. L'équipe principale a publié Japa un an plus tard. C'est un cadre de test agréable pour Node.js.

Quoi qu'il en soit, j'ai tellement appris sur le développement backend et l'architecture avec AdonisJS que les tests n'étaient clairement pas une priorité. C'était la première fois que je pouvais séparer les fonctionnalités et éviter les dépendances internes. Bien plus propre et plus facile à entretenir que Feathers.js ou NestJS. Oui, j'ai aussi essayé NestJS, mais ce framework est une véritable blague pour moi.

À la fin d'août 2021, je me suis lancé dans un nouveau projet annexe appelé Insamee, un écosystème étudiant. C'était le successeur de la plateforme de tutorat. L'architecture est importante à clarifier. À l'origine, c'était un serveur API avec AdonisJS et quatre applications front-end utilisant Nuxt. Trop lourd et trop compliqué, comme je le réalise maintenant.

Capture d'écran d'Insamee
Capture d'écran d'Insamee

À la fin de 2021, j'ai réécrit le projet depuis le début en utilisant uniquement Adonis et un seul serveur. Plus de tests.

J'ai réussi à me faire dispenser de cours pour me concentrer sur mon projet. Une véritable opportunité. Pour valider mon semestre, je devais écrire un rapport et faire une présentation en direct. Lors de la présentation, je n'ai pas parlé des tests (il n'y en avait pas). Cependant, le jury m'a interrogé à ce sujet. J'ai simplement dit que le runner n'était pas encore prêt, donc je n'avais écrit aucun test. Ils ont pris quelques minutes pour expliquer que les tests sont cruciaux, et j'ai clairement compris qu'un projet réel et professionnel doit avoir des tests—c'est non-négociable. Dans mon esprit, quelque chose a cliqué.

Par la suite, d'avril à août 2022, j'ai fait un stage de quatre mois dans une société de conseil à Paris. J'étais responsable de la création, depuis zéro, d'un panneau d'administration pour un projet de cluster de calcul hautes performances utilisant Angular 14. C'était une première pour moi. Je n'avais jamais utilisé Angular auparavant. J'ai mis des mois à saisir le concept de TestBed et le système d'injection de dépendances (DI). J'étais tellement perdu, mais plus tard, j'ai découvert que l'injection de dépendances est importante. Pendant cette période, j'ai appris des concepts mais je n'avais pas de pratique réelle.

En 2024, j'ai effectué mon stage de six mois dans une entreprise de cloud suisse. Je faisais partie d'une équipe développant des composants pour d'autres projets. C'est là que j'ai vraiment commencé à apprécier les tests. Tout d'abord, la couverture était de 90 %. Cela signifie que si la couverture tombe en dessous de 90 %, vous n'avez d'autre choix que d'écrire des tests pour soumettre un MR. Le projet avait huit mois, donc il y avait beaucoup de tests à référence. J'ai également écrit de nombreux tests de bout en bout en utilisant Playwright.

Dans le même temps, j'ai regardé un live stream de Romain Lanz discutant de "l'écriture de code testable" et expliquant le DI dans Adonis. Avec ces explications et les exemples concrets que j'ai rencontrés pendant mon stage, les choses ont commencé à s'assembler et à avoir du sens.

Cette année, à Devoxx 2024, j'ai essayé d'assister à autant de conférences que possible sur le TDD et le DDD. Entendre des experts discuter d'exemples du monde réel et expliquer comment ils architecturent leurs applications pour les rendre testables et à l'épreuve du temps a été véritablement éclairant.

Devoxx France 2024 en tant qu'Orateur

Malgré tout cela, j'ai encore du mal à trouver des exemples de tests authentiques et pratiques. C'est tellement frustrant de connaître la théorie mais de ne pas pouvoir l'appliquer.

Mais je crois que pour apprendre, il n'y a rien de plus efficace que d'agir. Alors, j'ai pris le framework que je connais le mieux, Nuxt, et j'ai commencé à expérimenter avec les outils de test disponibles. J'ai utilisé l'intégration de bout en bout avec Playwright, mais ce n'était pas une expérience agréable. Les tests prenaient trop de temps à s'exécuter. Étrangement, l'expérience pendant mon stage était l'opposée. J'ai découvert que l'intégration Nuxt reconstruit l'application avant chaque test. J'ai fait une PR pour permettre au runner de réutiliser un serveur existant.

Dans le même temps, j'ai reçu des retours sur mon fork du tracker Plausible. Certains bugs ont été signalés, et de nouvelles fonctionnalités ont été demandées. Le problème était que le projet n'avait pas de tests, et je me souvenais clairement à quel point cela avait été douloureux sur la plateforme de tutorat. J'ai également remarqué que je n'avais pas touché le projet depuis un certain temps, et que les tests manuels ne suffiraient pas pour garantir la qualité du projet à long terme. Lorsque vous n'avez pas touché un projet depuis un certain temps, vous devez tout réapprendre. Avec des tests fiables, vous pouvez être sûr que le projet fonctionne comme prévu, même après des refactorings, des corrections de bugs et de nouvelles fonctionnalités. J'ai décidé d'écrire des tests avant de toucher quoi que ce soit d'autre. Cela m'a pris trois jours, mais maintenant, je suis confiant que le projet fonctionne comme attendu. C'est satisfaisant, et il n'y a plus de crainte de toucher au projet.

Tests réussis du Plausible Tracker
Tests réussis du Plausible Tracker

Enfin, à peu près au même moment, j'ai découvert Laracasts. J'ai appris PHP et rencontré des tests dans Laravel. C'était révélateur. Les tests sont bien expliqués, écrire des tests, même avec une base de données, est facile, et cela s'exécute très rapidement. Écrire des tests avec Pest est vraiment agréable. Si vous ne l'avez jamais essayé, vous devriez.

Essayez Laracasts

Comprendre la Valeur des Tests

Il m'a fallu six ans pour saisir la valeur des tests et écrire des tests significatifs. Est-ce long ? Je ne sais pas. Cela démontre que l'apprentissage est un processus itératif, avec des échecs et des réessais. Avec de la persévérance, on finit par y arriver.

Tout au long de ce voyage, je me suis posé de nombreuses questions. Voici quelques réponses que j'aurais aimé trouver plus tôt.

  1. Les tests ne doivent pas être difficiles.
  2. Cependant, tester est plus compliqué que expect(1 + 1).toBe(2).
  3. La difficulté des tests est souvent liée à l'architecture de l'application et à la capacité de séparer les fonctionnalités. Plus l'application est modulaire, plus il est facile de tester.
  4. Avant de choisir un outil, considérez la capacité de tester votre application. Si vous ne pouvez pas le tester, ça ne vaut pas le coup.
  5. Aucun test n'est acceptable. C'est un compromis. Utilisez-le judicieusement.
  6. La règle 5 n'invalide pas les règles 4 ou 3. Si vous finissez par écrire des tests, vous vous en remercierez.
  7. Viser 100 % de couverture n'a pas de sens en soi. C'est un piège qui peut mener à de mauvaises pratiques.
  8. Les tests unitaires, d'intégration et de bout en bout ont de la valeur. Choisissez celui qui correspond le mieux à vos besoins.
  9. Structurez votre test avec la méthode arrange, act et assert. C'est une bonne méthode pour organiser vos tests et commencer à les écrire.
  10. Écrivez d'abord la partie assert.
  11. Pensez à un test comme une description d'une spécification de l'application. Mon application devrait afficher/faire cela lorsque cela se produit.
  12. Et le plus important, écrivez du code testable avant d'écrire des tests. Cette approche rendra votre code plus lisible, maintenable et testable.

Enfin, ne tentez pas de tout tester dès le premier jour de votre voyage. Commencez petit, apprenez, écrivez des tests unitaires simples, comprenez comment modulariser votre code et progressez à partir de là. C'est un long voyage, mais cela en vaut la peine.

J'apprends encore beaucoup, mais je suis confiant que je suis sur la bonne voie. À bientôt dans le prochain article !

Laracasts est la meilleure ressource que j'ai trouvée pour apprendre les tests. PHP est génial, et apprendre des concepts généraux est encore mieux. Essayez-le maintenant.

Retour aux articles
Soutenez mon travail
Suivez-moi sur