mai
2009
Je fais mes premiers pas en BDD – le Behavior Driven Development (développement dirigé par les comportements). A ce stade j’ai du mal à voir s’il s’agit d’une simple variante du TDD (développement dirigé par les tests), ou bien s’il s’agit de quelque chose de vraiment différent et novateur. Le BDD semble permettre de faire des tests à plus haut niveau que les tests unitaires, directement au niveau des histoires d’utilisateurs (user story), et produit un rapport d’exécution de test compréhensible par un non informaticien, contrairement aux tests unitaires usuels. Certains trouvent cela très utile pour démontrer à leurs clients qu’ils font bien les tests, et indiquer précisément quels tests ils font, pour d’autres c’est simplement un moyen d’implémenter des spécifications exécutables et avoir une meilleure couverture de code.
Pour se faire une idée plus précise de cette nouvelle technique, rien ne vaut une expérimentation concrète, je vais donc participer demain à une séance de BDD avec mes camarades du Coding Dojo du CARA. Voici comment j’ai préparé mon environnement Visual Studio 2008 pour pouvoir faire du BDD, dans une configuration minimaliste pour démarrer.
D’une part il faut télécharger la dernière version du framework de BDD NBehave, et pour cela il faut avoir un client Subversion comme Tortoise par exemple, et indiquer comme repository http://nbehave.googlecode.com/svn/trunk/. Attention, le site de NBehave propose bien une version à télécharger directement, mais c’est la version 0.3 qui date de 2007, donc il vaut mieux obtenir la dernière version via Subversion. Une fois NBehave téléchargé localement, il suffit de lancer NBehave 0.4.exe pour installer tout ce qu’il faut.
Ensuite il faut obtenir NUnit, par exemple la version 2.5 disponible depuis début mai.
Il faut ensuite créer un projet C#, et indiquer comme références NBehave.Narrator.Framework et nunit.framework. Les fichiers devront déclarer les namespaces correspondants :
using NBehave.Narrator.Framework; using NUnit.Framework;
NBehave s’utilise en ligne de commande, et produit son rapport de test sous forme d’un fichier, aussi j’ai simplement ajouté un événement de post-build,
"C:\Program Files\NBehave\0.4\NBehave-Console.exe" $(TargetPath) /storyOutput=$(SolutionDir)\nbehave-output.txt
Garder le fichier nbehave-output.txt ouvert dans l’IDE permet d’être notifié des changements à chaque build. Il y a certainement des intégrations IDE plus poussées (peut-être avec Gallio mais je n’ai pas encore fait l’essai).
On peut alors passer à la programmation des « spécifications exécutables ». Tout d’abord il faut créer une classe qui va permettre de regrouper plusieurs histoires d’utilisateur, et qui doit être identifiée par l’attribut Theme comme suit :
[Theme("Demonstrating NBehave")] public class Example
Ne pas oublier de déclarer la classe publique, j’ai perdu pas mal de temps à cause de cela, NBehave ne trouve sinon aucune classe à exécuter.
Les méthodes publiques de cette classe doivent porter l’attribut Story, et ce sont elles qui contiennent le code de test :
[Story] public void basic_demonstration() { var story = new Story("Basic demonstration"); story .AsA("developer") .IWant("to demonstrate NBehave in a small project") .SoThat("we can start programming quickly during the dojo");
Le code de test repose sur l’utilisation d’une instance de la classe Story, que l’on documente grâce aux méthodes AsA, IWant, SoThat, ce qui permet de respecter le format habituel des histoires utilisateurs, par exemple : en tant que (client de la banque) je veux (pouvoir retirer de l’argent en dehors des heures d’ouverture) pour (me dépanner le week-end). Ces trois méthodes ne servent qu’à documenter, elles n’ont pas d’influence sur l’exécution.
La partie réellement exécutable arrive ensuite, et commence par indiquer de quel scénario il s’agit (une histoire d’utilisateur peut comporter plusieurs scénarios) :
story .WithScenario("Demonstrating basic NBehave syntax with stack-based calculations") .Given("Two numbers", 10, 20, (a, b) => { stack.Push(a); stack.Push(b); }) .When("I add them", () => stack.Push(stack.Pop() + stack.Pop())) .Then("I should get", 30, expectedValue => Assert.AreEqual(expectedValue, stack.Pop()))
La variable stack est du type Stack de int, et est déclarée au même niveau que la variable story.
Le scénario se décompose alors en trois étapes : Given (le point de départ, le contexte initial), When (action que l’on effectue), Then (ce que l’on veut vérifier). A ce moment-là il faut bien introduire du « vrai » code, et c’est fait grâce à des fonctions anonymes ou expressions lambdas. Le code soumis au test ci-dessus n’est pas très significatif, j’ai juste introduit la pile stack pour m’amuser. Notez que la partie Then comporte des assertions du type NUnit.
En cas de succès, l’exécution du code ci-dessous donne :
Theme: Demonstrating NBehave Story: Basic demonstration Narrative: As a developer I want to demonstrate NBehave in a small project So that we can start programming quickly during the dojo Scenario 1: Demonstrating basic NBehave syntax with stack-based calculations Given Two numbers: (10, 20) When I add them Then I should get: 30
Scenario 1: Demonstrating basic NBehave syntax with stack-based calculations Given Two numbers: (10, 20) When I add them Then I should get: 300 - FAILED
(bien sûr c’est le test qui est faux ici !)
On peut également enrichir la partie When et la partie Then grâce à une méthode And, comme ci-dessous :
.WithScenario("Demonstrating syntax variant (.And) with stack-based calculations") .Given("A number", 10, stack.Push) .And("Another number", 20, stack.Push) .When("I multiply them", () => stack.Push(stack.Pop()*stack.Pop())) .Then("I should get", 200, expectedValue => Assert.AreEqual(expectedValue, stack.Pop())) .And("the number of items in the stack should be",0, expectedValue => Assert.AreEqual(expectedValue,stack.Count))
ce qui donne le rapport suivant :
Scenario 2: Demonstrating syntax variant (.And) with stack-based calculations Given A number: 10 And Another number: 20 When I multiply them Then I should get: 200 And the number of items in the stack should be: 0
Les scénarios sont chaînés les uns aux autres, ce qui permet de factoriser la description de l’histoire utilisateur, mais avec l’inconvénient qu’un scénario qui échoue empêche les suivants de s’exécuter. On retrouve alors le même genre d’inconvénient que celui d’une méthode de test unitaire qui comporte trop d’assertions.
Enfin il est possible d’alléger la syntaxe des assertions grâce à des méthodes d’extension, ce qui permet par exemple d’avoir
.And("the number of items in the stack should be zero", () => stack.Count.ShouldEqual(0))
au lieu de
.And("the number of items in the stack should be",0, expectedValue => Assert.AreEqual(expectedValue,stack.Count))
Toutefois je n’ai pas encore réussi à utiliser correctement cette option, mes tests échouent systématiquement.
Voici donc mes premiers essais, qui m’ont plutôt laissé une bonne impression. J’étais un peu sceptique devant le caractère assez verbeux de ces tests, mais en fait il n’y a pas de duplication d’information (ce qui constituerait une violation du principe DRY), puisqu’il n’est pas nécessaire de coder en dur les arguments dans les chaînes de caractères. On introduit donc des sortes de commentaires exécutables, qui seront effectivement lus par les développeurs et les testeurs, donc on n’est pas du tout dans une situation de mauvais commentaires. Cela m’a donc donné envie d’aller plus loin dans le BDD et de voir ce que cela donne dans des cas plus réalistes.
Références : ce tutoriel en anglais m’a permis de comprendre les principes de base.
Commentaires récents
- Des tableaux pour l’intégration d’un équipier dans une équipe Scrum dans
- Rétrospectives, la directive première dans
- Des tableaux pour l’intégration d’un équipier dans une équipe Scrum dans
- Des tableaux pour l’intégration d’un équipier dans une équipe Scrum dans
- Des tableaux pour l’intégration d’un équipier dans une équipe Scrum dans