janvier
2010
Pour démarrer la discussion sur la rédaction de spécifications exécutables l’autre soir au CARA, j’avais pris l’exemple de la réalisation d’un logiciel de comptage de calories. L’exemple n’est pas gratuit : je trouve que la plupart des logiciels de ce genre ont une interface utilisateur très influencée par l’implémentation avec une base de données, et pas très influencée par les besoins utilisateurs. Pardonnez l’expression, mais ce genre de logiciel « pue la base de données », alors que la base de donnée n’a que peu de valeur pour l’utilisateur – c’est juste un choix d’implémentation bien pratique pour les développeurs.
Par conséquent, il me semble que travailler sur un tel logiciel est intéressant pour apprendre le BDD, car justement nous aimerions éviter de « polluer » les scénarios par des concepts purement techniques comme des questions de base de données. Le challenge est donc de parvenir à exprimer les scénarios avec des mots d’utilisateur et pas du jargon informatique.
Dans ce billet je vais détailler mon point de départ et illustrer les premières leçons que j’ai tirées des discussions. Bien sûr cela reste un exemple « jouet » mais je parviendrai peut-être à en faire une application complète dans quelque temps.
Je suis également passé à la rédaction de scénarios en français, ce qui est possible en Cucumber (tapez la commande cucumber -i18n fr pour avoir les équivalences).
Mon point de départ était la fonctionnalité suivante :
Les discussions ont fait apparaître que « qui veut perdre du poids » n’avait aucune influence pour le moment (je voulais souligner que cela pourrait avoir de l’importance dans de futures fonctionnalités, mais cette information superflue ici rend simplement plus difficile la compréhension de la fonctionnalité). Donc une leçon à retenir : ne pas « sur-spécifier » !
J’ai ensuite écrit de très petits scénarios comme :
ce qui m’a permit ensuite d’écrire un scénario couvrant une journée complète (les lignes commençant par # sont des commentaires) :
et ainsi de suite. Cela m’a permis d’arriver un scénario vérifiant la fonctionnalité « connaître le nombre total de calories pour la journée ».
Les petits scénarios m’ont été utiles pour mettre au point le code derrière tout cela en faisant de petits pas (tout comme quand on pratique le TDD). Mais ils contribuent à l’impression de « verbosité excessive » que mes scénarios ont donné durant la présentation, donc il faut surement accepter de les supprimer une fois que leur intention a été capturée par ailleurs dans le scénario final.
J’ai ensuite travaillé sur une deuxième fonctionnalité :
Fonctionnalité que j’ai souhaité vérifier de la manière suivante :
Un point qui a posé problème est que je ne vérifie pas la totalité du contenu de la table des aliments – cela ne me paraît pas nécessaire. Mais mes attentes sont peu claires pour le lecteur, et pour lever l’ambiguïté il aurait sans doute suffit d’écrire « la table des aliments contient notamment: ». Il faut donc rédiger les vérifications avec beaucoup de précision et de clarté.
Un deuxième problème est que mon scénario est bien plus long que la copie d’écran ci-dessus – en effet j’avais à nouveau couvert avec mes exemples une journée complète. Ce qui est inutile vu que le calcul pour la journée était déjà couvert par la vérification de la fonctionnalité précédente ! Donc une autre leçon à retenir est de ne pas « sur-vérifier ».
La troisième fonctionnalité consistait à travailler à partir d’une table pré-remplie :
etc.
Mes scénarios ci-dessus ont suscité un vif débat, car ils semblent représenter un effort démesuré par rapport au volume de code qui est testé derrière. En effet il s’agit d’implémenter une simple règle de trois, de parcourir des listes, d’utiliser une table de hachage. Je pense que la critique était justifiée, et que mes scénarios auraient pu contenir moins d’information, mais que cela ne remet pas en cause l’intérêt du BDD et l’intérêt de travailler avec des exemples (ces derniers méritant certainement d’être réhabilités dans nos processus de développement logiciel – voir mon billet précédent).
Un autre élément de débat était de savoir comment se positionner par rapport à une éventuelle interface graphique. Etant donné la manière dont j’ai écrit et réalisé les fonctionnalités, l’application n’existe pas encore, je suis simplement en train de mettre au point une bibliothèque de classes, indépendamment de la manière dont elles seront ultérieurement présentées à un utilisateur. Cette approche était confortable par certains, mais d’autres auraient préféré travailler à partir d’une interface graphique (le risque étant de mettre au point un bon modèle métier bien testé, mais de ne pas satisfaire les utilisateurs qui veulent voir très vite une interface graphique).
Je pense que le schéma suivant de DDD Vite Fait illustre l’endroit où j’essaie de me placer pour l’instant – mes scénarios concernent le domaine, et pas l’application, ce qui est source de confusion :
Pour finir ce billet, au cas ou vous soyez curieux de ce qu’il y a derrière (c’est généralement le cas quand je présente les scénarios), je vous montre le code que j’ai écrit pour le pont entre les scénarios exécutables et les classes du domaine :
# encoding: UTF-8
require 'F:\ruby\cucumber\comptageCalories\ComptageCalories\compteur_calories.rb'
require 'F:\ruby\cucumber\comptageCalories\ComptageCalories\table_aliments.rb'
Before do
@compteur = CompteurCalories.new
@table_aliments = TableAliments.new
end
When /^le nombre total de calories devrait être (.*)$/ do |calories|
#puts @compteur.total_calories
@compteur.total_calories.should be_equal calories.to_i
end
When /^une table des aliments vide$/ do
#rien
end
When /^la table des aliments contient:$/ do |table|
# table is a | confiture | 1 | cuillère à café | 30 |
table.hashes.each do |hash|
reference = @table_aliments.reference(hash["aliment"])
reference.quantite.should == hash["quantite"]
reference.unite.should == hash["unite"]
reference.calories.should == hash["calories"]
end
end
When /^je mange (\d+\.?\d*) (.*) (de\s+|d')(.*\w|.*%) \((\d+) (.*) valant (.*) calories\)$/ do |quantite_dans_unite,unite1, de, nom_aliment,reference_dans_unite,unite2,reference_en_calories|
@table_aliments.ajoute(nom_aliment, reference_dans_unite,unite2, reference_en_calories)
@compteur.ajoute(nom_aliment,quantite_dans_unite, reference_dans_unite, reference_en_calories)
end
When /^je mange (\d+\.?\d*) (.*) (de\s+|d')(.*\w|.*%)$/ do |quantite_dans_unite,unite1, de, nom_aliment|
reference = @table_aliments.reference(nom_aliment)
reference_dans_unite = reference.quantite
reference_en_calories = reference.calories
@compteur.ajoute(nom_aliment,quantite_dans_unite, reference_dans_unite, reference_en_calories)
end
When /^la table des aliments suivante:$/ do |table|
# table is a | confiture | 1 | cuillère à café | 30 |
table.hashes.each do |hash|
@table_aliments.ajoute(hash["aliment"],hash["quantite"],hash["unite"],hash["calories"])
end
end
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