février
2010
Avant de passer au compte-rendu du dojo d’aujourd’hui, voici un nouvel éclairage sur l’articulation entre TDD et BDD, que j’emprunte à ce billet Behavior Driven Development with NBehave (trouvé grâce à cet autre billet Bien Tester une application Asp.net MVC sur le BDD par Guillaume Saint Etienne).
Je pense que dans les dojos précédents nous avons bien compris et bien pratiqué le cycle RED/GREEN/REFACTOR du TDD :
Nous voulons maintenant comprendre comment cela s’articule avec le BDD (partant du principe que nous sommes convaincus que cela vaut le coup de s’intéresser au BDD).
Je trouve que le schéma ci-dessous de Behavior Driven Development with NBehave illustre assez bien la manière de procéder, du BDD vers le TDD :
Voici comment je comprends ce schéma :
- On se laisse donc guider par une user story (ce qui évite un risque du TDD qui est de s’égarer sur des questions peu pertinentes pour l’utilisateur final, étant donné que l’on travaille très près du code).
- La première étape est d’avoir des scénarios « Pending » – en Cucumber cela correspond au premier fragment de pont que Cucumber génère automatiquement.
- En implémentant ces scénarios, on définit itérativement l’API des classes métier. Pour cela on suit un cycle RED/GREEN/REFACTOR au niveau de l’outil de BDD, et on ne cherche pas nécessairement à implémenter le comportement réel et final : on peut utiliser des comportements « codés en dur », ou simplistes – c’est ce qu’illustrait le dojo précédent finalement. C’est la partie « Stubbed Scenario » ci-dessus.
- Une fois que l’API est stable, on implémente le comportement réel, et pour cela on pratique le RED/GREEN/REFACTOR en changeant d’outil : cette fois on fait du TDD avec un outil du type xUnit.
- Et l’on obtient un test d’acceptance (sous forme de scénario exécutable) à la fin du processus.
Aujourd’hui dans le dojo nous avons travaillé essentiellement au niveau « Real Behavior », l’API de notre classe CalibrationCurve avait en effet été stabilisée grâce au travail de la dernière fois, nous n’avons pas eu à la retoucher. Par contre nous avons complètement reprogrammé son comportement interne.
Pour commencer à programmer, nous nous sommes appuyés sur le test unitaire suivant :
class LinearRegressionTest ? Test::Unit::TestCase
def setup
@curve = CalibrationCurve.new
@curve.ajoute_points(0, 10)
@curve.ajoute_points(10, 100)
end
def test_calcul_slope
assert_in_delta(0.111, @curve.calcule_slope, 0.001)
end
def test_calcule_ordinate
assert_in_delta(-1.111, @curve.calcule_ordinate, 0.001)
end
end
(désolé, pour d’obscures raisons le moteur de blog ne me laisse pas insérer le caractère « inférieur », et j’ai mis ≤ à la place dans les morceaux de code qui en avaient besoin)
On peut noter que nous n’avons pas inventé de nouvelles données du test, nous avons simplement utilisé les données déjà présentes dans le scénario qui échouait à la fin de la dernière séance :
Avec le recul, on peut se demander si ce test unitaire n’est pas complètement redondant avec le scénario, et s’il présente vraiment de l’intérêt ! En effet nous exerçons l’API de notre classe exactement comme dans le pont entre le scénario et la classe CalibrationCurve.
Par contre, en programmant les calculs nous avons identifié un cas limite (un seul point fourni par l’utilisateur) et nous avons dû prendre une décision sur le comportement attendu. Voici un test unitaire qui couvre ce cas :
class LinearRegressionTestLimite ? Test::Unit::TestCase
def setu<
@curve = CalibrationCurve.new
@curve.ajoute_points(0, 0)
end
def test_slope_should_be_zero
assert_equal(@curve.calcule_slope, 0)
end
def test_ordinate_should_be_zero
assert_equal(@curve.calcule_ordinate, 0)
end
end
On peut alors se demander si cette information doit rester « cachée » dans un test unitaire, ou bien s’il faut la faire remonter à l’utilisateur, via un complément de scénario. La décision dépend surement d’un dialogue avec les utilisateurs : est-ce que ce cas limite est pertinent pour eux ou non ? est-ce qu’ils sont intéressés par spécifier le comportement attendu ?
En tout cas ce petit exemple nous indique déjà que le schéma ci-dessus Story ==> Acceptance Test n’est pas si linéaire que cela : en travaillant en TDD, nous pouvons découvrir des compléments de scénarios !
Sur un point plus technique, nous avons également éprouvé le besoin d’un test unitaire pour nous assurer du bon fonctionnement de notre extension du module Enumerable, auquel nous avons ajouté une méthode mean comme ci-dessous :
module Enumerable
def mean
sum = self.inject(0.0) do |sum, xi|
sum = sum + xi
end
return sum / self.size
end
end
Comme nous ne connaissions pas suffisamment le principe du inject, le test unitaire nous a rassuré sur le bon fonctionnement de notre extension mean :
class EnumerableTest ? Test::Unit::TestCase
def setup
@array = [10, 100]
end
def test_mean
assert_equal(55, @array.mean)
end
end
En ce qui me concerne, je garderais comme test unitaire uniquement ce dernier point très technique, et je me contenterais du scénario et d’un complément de scénario pour le cas limite. Et vous, comment feriez-vous ?
Pour finir, le code de CalibrationCurve qui passe avec succès nos scénarios et tests unitaires :
class CalibrationCurve
def initialize
@x = []
@y = []
end
def compute
xbar = @x.mean
ybar = @y.mean
sx2= @x.map{|xi| (xi-xbar)**2}.mean
sum = 0
@x.each_with_index do |xi, i|
sum = sum + (@x[i]-xbar)*(@y[i]-ybar)
end
sxy = sum / @x.size
if sx2.abs != 0 then
@a = sxy / sx2
@b = ybar - @a*xbar
else
@a = 0
@b = 0
end
end
def ajoute_points (area,concentration)
@x.push(concentration)
@y.push(area)
compute
end
def calcul_concentration(area)
return (area - @b) / @a
end
def calcule_slope
return @a
end
def calcule_ordinate
return @b
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