20 Novembre 2020
Le test du logiciel est une discipline qui vise à s'assurer que le logiciel répond à ses objectifs prédéfinis. La planification de cette phase de test commence tôt dans le cycle de vie du développement et elle est d'une importance capitale.
L'activité de test utilise différents types et techniques de tests pour vérifier que le logiciel est conforme à son cahier des charges ou à ses spécifications (vérification du produit) et aux attentes du client (validation du produit), dans cet article nous allons découvrir ensemble les deux techniques de tests les plus utilisées par les équipes IT : TDD et BDD.
Le développement piloté par les tests (TDD) est un processus de développement logiciel qui repose sur la répétition d'un cycle de développement court : les exigences se transforment en cas de test très spécifiques. Le code est écrit pour que le test réussisse. Enfin, le code est remanié et amélioré pour garantir la qualité du code et éliminer toute dette technique. Ce cycle est bien connu sous le nom de cycle Red-Green-Refactor.
Dans le cycle Red-Green-Refactor. Nous commençons par un test qui échoue (rouge) et implémentons aussi peu de code que nécessaire pour le faire passer en (vert). Ce processus est également connu sous le nom de Test-First Development. TDD ajoute également une étape Refactor, qui est tout aussi importante pour le succès global.
Le diagramme ci-dessus fait un excellent travail en donnant un aperçu facile à digérer du processus. Cependant, la beauté réside dans les détails. Avant d'approfondir chaque étape individuelle, nous devons également discuter de deux approches de haut niveau du TDD, à savoir le TDD ascendant et descendant.
L'idée derrière le TDD ascendant, également connu sous le nom de TDD Inside-Out, est de créer des fonctionnalités de manière itérative, en se concentrant sur une entité à la fois, en solidifiant son comportement avant de passer à d'autres entités et à d'autres couches.
Nous commençons par écrire des tests de niveau Unité, procédons à leur implémentation, puis passons à l'écriture de tests de niveau supérieur qui agrègent les fonctionnalités des tests de niveau inférieur, créent une implémentation dudit test agrégé, et ainsi de suite. En construisant, couche par couche, nous arriverons finalement à une étape où le test global est un test de niveau d'acceptation, qui, espérons-le, correspond à la fonctionnalité demandée. Ce processus en fait une approche hautement centrée sur le développeur, principalement destinée à faciliter la vie du développeur.
Avantages | Inconvénients |
---|---|
L'accent est mis sur une entité fonctionnelle à la fois | Retarde l'étape d'intégration |
Les entités fonctionnelles sont faciles à identifier | L'ampleur du comportement qu'une entité doit exposer n'est pas claire |
Une vision de haut niveau n'est pas nécessaire pour démarrer | Risque élevé d'entités n'interagissant pas correctement les unes avec les autres |
Aide à la parallélisation | La logique métier peut être étendue à plusieurs entités, ce qui la rend peu claire et difficile à tester |
Le TDD descendant est également connu sous le nom de TDD Outside-In ou Développement piloté par les tests d'acceptation (ATDD). Il adopte l'approche inverse. Dans lequel nous commençons à construire un système, en ajoutant de manière itérative plus de détails à la mise en œuvre. Et le décomposer de manière itérative en entités plus petites à mesure que les opportunités de refactoring deviennent évidentes.
Nous commençons par écrire un test de niveau d'acceptation, procédons avec une implémentation minimale. Ce test doit également être effectué de manière incrémentielle. Ainsi, avant de créer une nouvelle entité ou méthode, elle doit être précédée d'un test au niveau approprié. Nous affinons donc itérativement la solution jusqu'à ce qu'elle résout le problème qui a lancé tout l'exercice, c'est-à-dire le test d'acceptation.
Avantages | Inconvénients |
---|---|
L'accent est mis sur un scénario demandé par l'utilisateur à la fois | Critique pour obtenir le bon test d'assertion, nécessitant ainsi une discussion collaborative entre l'entreprise / l'utilisateur / le client et l'équipe |
L'accent est mis sur l'intégration plutôt que sur les détails de mise en œuvre | Démarrage plus lent car le flux est identifié par plusieurs itérations |
La quantité de comportement qu'une entité doit exposer est claire | Possibilités de parallélisation plus limitées jusqu'à ce qu'un système squelette commence à émerger |
Les exigences de l'utilisateur, la conception du système et les détails de mise en œuvre sont tous clairement reflétés dans la suite de tests |
Cette configuration fait du TDD descendant une approche plus centrée sur l'entreprise / le client. Cette approche est plus difficile à faire car elle repose largement sur une bonne communication entre le client et l'équipe. Cela nécessite également une bonne citoyenneté de la part du développeur, car la prochaine étape itérative doit être soigneusement étudiée. Ce processus s'accélérera avec le temps mais a une courbe d'apprentissage. Cependant, les avantages l'emportent de loin sur les inconvénients. Cette approche se traduit par une collaboration entre le client et l'équipe qui occupe le devant de la scène, un système avec un comportement très bien défini, des flux clairement définis, l'accent est mis sur l'intégration en premier et un résultat très prévisible.
Forts de la vision de haut niveau évoquée ci-dessus sur la façon dont nous pouvons aborder le TDD, nous sommes libres d'approfondir les trois étapes fondamentales du flux rouge-vert-refactor.
Nous commençons par écrire un seul test, l'exécutons (le faisant échouer ainsi) et seulement ensuite passons à l'implémentation de ce test. Ecrire le bon test est ici crucial, tout comme s'entendre sur la couche de test que nous essayons d'atteindre. S'agira-t-il d'un test de niveau d'acceptation ou d'un test de niveau unitaire ? Ce choix est la principale délimitation entre TDD ascendant et descendant.
Lors de la phase verte, nous devons créer une implémentation pour que le test défini dans la phase rouge passe. L'implémentation doit être l'implémentation la plus minimale possible, faisant passer le test et rien de plus. Exécutez le test et regardez-le réussir.
Créer l'implémentation la plus minimale possible est souvent le défi ici car un développeur peut être enclin, par la force de son habitude, à embellir l'implémentation dès le départ. Ce résultat n'est pas souhaitable car il créera un bagage technique qui, au fil du temps, rendra le refactoring plus coûteux et faussera potentiellement le système en fonction du coût de refactoring. En gardant chaque étape de mise en œuvre aussi petite que possible, nous soulignons davantage la nature itérative du processus que nous essayons de mettre en œuvre. C'est cette fonctionnalité qui nous donnera de l'agilité.
Un autre aspect clé est que le stade rouge, c'est-à-dire les tests, est ce qui anime le stade vert. Il ne devrait y avoir aucune implémentation qui ne soit pilotée par un test très spécifique. Si nous suivons une approche ascendante, cela vient à peu près naturellement. Cependant, si nous adoptons une approche descendante, nous devons être un peu plus consciencieux et nous assurer de créer d'autres tests au fur et à mesure que l'implémentation prend forme, passant ainsi des tests de niveau d'acceptation aux tests de niveau unité.
L'étape Refactor est le troisième pilier du TDD. Ici, l'objectif est de revoir et d'améliorer la mise en œuvre. L'implémentation est optimisée, la qualité du code est améliorée et la redondance éliminée.
Le refactoring peut avoir une connotation négative pour beaucoup, étant perçu comme un coût pur, corrigeant quelque chose de mal fait la première fois. Cette perception trouve son origine dans des flux de travail plus traditionnels où le refactoring est principalement effectué uniquement lorsque cela est nécessaire, généralement lorsque la quantité de bagages techniques atteint des niveaux intenables, ce qui entraîne un effort de refactoring long et coûteux.
Ici, cependant, la refactorisation fait partie intégrante du flux de travail et est effectuée de manière itérative. Cette flexibilité réduit considérablement le coût du refactoring. Le code n'est pas entièrement retravaillé. Au lieu de cela, il évolue lentement. De plus, le code refactoré est, par définition, couvert par un test. Un test qui a déjà réussi dans une itération précédente du code. Ainsi, la refactorisation peut être effectuée en toute confiance, ce qui se traduit par une accélération supplémentaire. De plus, cette approche itérative d'amélioration de la base de code permet une conception émergente, ce qui réduit considérablement le risque de sur-ingénierie du problème.
Il est d'une importance cruciale que le comportement ne change pas et nous n'ajoutons pas de fonctionnalités supplémentaires pendant la phase de refactorisation. Ce processus permet de refactoriser avec une confiance et une agilité extrême car le code concerné est, par définition, déjà couvert par un test.
Comme vous l’aurez compris, la méthodologie TDD suit donc un processus simple en 6 étapes :
Supposons que nous ayons l'obligation de développer une fonctionnalité de connexion pour une application qui a comme champs, le nom d'utilisateur, le mot de passe et un bouton d'envoi.
Étape 1 : Créez un scénario de test.
Étape 2 : Exécutez ce scénario de test et nous obtiendrons une erreur indiquant que la page de connexion n'est pas définie et qu'il n'y a pas de méthodes avec des noms enterUserName, enterPassword .
Étape 3 : Développez le code pour ce cas de test. Écrivons le code sous-jacent qui entrera le nom d'utilisateur et le mot de passe et obtiendra un objet de page d'accueil lorsqu'ils sont corrects.
Étape 4 : Exécutez à nouveau le cas de test et nous obtiendrons une instance de la page d'accueil.
Étape 5 : Refactorisons le code pour donner les bons messages d'erreur lorsque les conditions if de la méthode de soumission ne sont pas vraies.
Étape 6 : Écrivons maintenant un nouveau scénario de test avec un nom d'utilisateur et un mot de passe vides.
Maintenant, si vous essayez d'exécuter ce scénario de test, il échouera. Répétez les étapes 1 à 5 pour ce scénario de test, puis ajoutez la fonctionnalité permettant de gérer les chaînes de nom d'utilisateur et de mot de passe vides.
Le développement axé sur le comportement (BDD) est un processus de développement logiciel qui encourage la collaboration entre toutes les parties impliquées dans la livraison d'un projet. Il encourage la définition et la formalisation du comportement d'un système dans un langage commun compris par toutes les parties et utilise cette définition comme base d'un processus basé sur le TDD.
En effet, les expériences vécues avec TDD et ATDD l'ont conduit à proposer le concept BDD, dont l'idée et la revendication étaient de rassembler les meilleurs aspects de TDD et ATDD tout en éliminant les pains points qu'il a identifiés dans les deux autres approches. Ce qu'il a identifié, c'est qu'il était utile d'avoir des noms de test descriptifs et que le comportement de test était beaucoup plus précieux que le test fonctionnel.
Dan North fait un excellent travail en décrivant le BDD comme «Utilisation d'exemples à plusieurs niveaux pour créer une compréhension partagée et une certitude de surface afin de fournir des logiciels qui comptent.»
Quelques points clés ici :
BDD met encore plus l'accent sur la collaboration fructueuse entre le client et l'équipe. Il devient encore plus critique de définir correctement le comportement du système, résultant ainsi en des tests comportementaux corrects. Un piège courant ici est de faire des hypothèses sur la façon dont le système va mettre en œuvre un comportement. Cette erreur se produit dans un test qui est entaché de détails d'implémentation, ce qui en fait un test fonctionnel et non un vrai test comportemental. Cette erreur est quelque chose que nous voulons éviter.
La valeur d'un test comportemental est qu'il teste le système. Il ne se soucie pas de la façon dont il obtient les résultats. Cette configuration signifie qu'un test comportemental ne doit pas changer avec le temps. Sauf si le comportement lui-même doit changer dans le cadre d'une demande de fonctionnalité. Le rapport coût-bénéfice par rapport aux tests fonctionnels est plus important car ces tests sont souvent si étroitement associés à la mise en œuvre qu'un refactor du code implique également un refactor du test.
Cependant, l'avantage le plus substantiel est le maintien de la certitude de surface. Dans un test fonctionnel, un code-refactor peut également nécessiter un test-refactor, ce qui entraîne inévitablement une perte de confiance. Si le test échoue, nous ne savons pas quelle en est la cause : le code, le test ou les deux. Même si le test réussit, nous ne pouvons pas être sûrs que le comportement précédent a été conservé. Tout ce que nous savons, c'est que le test correspond à l'implémentation. Ce résultat est de faible valeur car, finalement, ce qui intéresse le client, c'est le comportement du système. C'est donc le comportement du système que nous devons tester et garantir.
En comparant directement TDD et BDD, les principaux changements sont les suivants :
Un écosystème de cadres et d'outils a émergé pour permettre une collaboration basée sur un langage commun entre les équipes. Ainsi que l'intégration et l'exécution de comportements tels que des tests en tirant parti des outils standard de l'industrie. Des exemples de cela incluent Cucumber, JBehave et Fitnesse, pour n'en nommer que quelques-uns.
Le processus impliqué dans la méthodologie BDD comprend également 6 étapes et est très similaire à celui du TDD.
Gardons le même exemple utilisé pour la partie TDD. Supposons que nous ayons l'obligation de développer une fonctionnalité de connexion pour une application qui a comme champs le nom d'utilisateur le mot de passe et un bouton d'envoi.
Étape 1 : écrivez le comportement de l'application pour saisir le nom d'utilisateur et le mot de passe.
Étape 2 : écrivez le script de test automatisé pour ce comportement comme indiqué ci-dessous.
Étape 3 : implémentez le code fonctionnel (ceci est similaire au code fonctionnel de l'étape 3 de l'exemple TDD).
Étape 4 : Exécutez ce comportement et voyez s'il réussit. S'il réussit, passez à l'étape 5, sinon déboguez l'implémentation fonctionnelle et réexécutez-la.
Étape 5 : Refactoriser l'implémentation est une étape facultative et dans ce cas, nous pouvons refactoriser le code dans la méthode de soumission pour imprimer les messages d'erreur comme indiqué à l'étape 5 pour l'exemple TDD.
Étape 6 : Écrivez un comportement différent et suivez les étapes 1 à 5 pour ce nouveau comportement.
TDD | BDD | |
---|---|---|
Concentrer sur | Livraison d'une fonctionnalité | Respecter le comportement attendu du système |
Approche | De bas en haut ou de haut en bas (développement basé sur les tests d'acceptation) | De haut en bas |
Point de départ | Un cas de test | Une user story / scénario |
Les participants | Équipe technique | Tous les membres de l'équipe, y compris le client |
Langue | Langage de programmation | Langage humain |
Processus | Lean, itératif | Lean, itératif |
Délivre | Un système fonctionnel qui répond à nos critères de test | Un système qui se comporte comme prévu et une suite de tests qui décrit le comportement du système en langage commun humain |
Évite | Sur-ingénierie, faible couverture des tests et tests de faible valeur | Écart par rapport au comportement prévu du système |
Fragilité | Un changement de mise en œuvre peut entraîner des modifications de la suite de tests | La suite de tests ne doit changer que si le comportement du système doit changer |
Difficulté de mise en œuvre | Relativement simple pour l’ascendant, plus difficile pour le descendant | La plus grande courbe d'apprentissage pour toutes les parties impliquées |
En fin de compte, la question ne devrait pas être de savoir s'il faut adopter le TDD ou le BDD, mais quelle approche est la meilleure pour la tâche à accomplir. Très souvent, la réponse à cette question sera les deux. Au fur et à mesure que de plus en plus de personnes participent à des projets plus importants, il deviendra évident que les deux approches sont nécessaires à différents niveaux et à différents moments tout au long du cycle de vie du projet. TDD donnera structure et confiance à l'équipe technique. Alors que BDD facilitera et mettra l'accent sur la communication entre toutes les parties concernées et fournira en fin de compte un produit qui répond aux attentes du client et offre la certitude de surface nécessaire pour garantir la confiance dans l'évolution future du produit.
Comme c'est souvent le cas, il n'y a pas de solution miracle ici. Nous avons plutôt deux approches très valables. La connaissance des deux permettra aux équipes de déterminer la meilleure méthode en fonction des besoins du projet. Une expérience supplémentaire et une fluidité d'exécution permettront à l'équipe d'utiliser tous les outils de sa boîte à outils au fur et à mesure que le besoin se fait sentir tout au long du cycle de vie du projet, obtenant ainsi le meilleur résultat commercial possible.