Il y a maintenant plus d’un an, je suis arrivé sur un projet qui, je le pense, a profondément changé ma façon de voir le monde du développement. De nombreuses techniques, liées à l’agilité, m’ont fait comprendre comment nous pouvions, nous, développeurs, nous épanouir dans notre travail tout en produisant des logiciels qui fonctionnent.
Deux techniques m’ont particulièrement marqué : Le Test Driven Developpement (TDD) et le Behavior Driven Development (BDD). C’est cette deuxième technique dont je souhaite parler ici.
Le BDD, ça sert à quoi ?
Derrière le terme anglais, on retrouve les notions de comportement et de fonctionnalité d’une application. Cette technique va nous permettre de lier, dans notre projet de développement, des demandes de l’équipe fonctionnelle. Ces demandes vont être exécutées de façon automatique dans le but de vérifier le bon fonctionnement d’un comportement ou d’une fonctionnalité (On les retrouve souvent sous le terme de tests d’acceptante).
L’une des problématiques que l’on retrouve dans tous les projets de développement, c’est le passage d’une spécification rédigée pendant des semaines, mois, années (Rayer les mentions inutiles) par des experts du métier et/ou des consultants fonctionnels. Ces spécifications sont ensuite transmises à l’équipe de développement. Cette interaction passe en grande partie par une transmission de document, une lecture du développeur et nécessairement une partie d’adaptation, mais surtout d’interprétation. Une grande partie de cette problématique est solutionnée par l’utilisation des méthodes Agile et plus particulièrement par des concepts comme la conception incrémentale et l’écriture d’histoire utilisateur.
Le BDD va intervenir lors de l’écriture des cas de tests d’une histoire utilisateur. Il va permettre à l’expert métier, au consultant fonctionnel et au développeur de se comprendre, via un langage naturel :
- On utilise des phrases, dans la langue du projet.
- On parle de besoin et non de solution, dans un langage non technique.
- On utilise des termes provenant du langage omniprésent partagé par tous (Vous le retrouvez souvent sous le terme d’Ubiquitous Language).
La finalité du point de vue logiciel est multiple :
- Le code créé est à l’image de la demande de l’utilisateur.
- La fonctionnalité est à l’image de la demande de l’utilisateur (On ne code que la fonctionnalité et rien de plus).
- L’implication et la réflexion des experts métier avec les développeurs permet de débloquer un grand nombre de problème non couvert et assurent une grande qualité de production.
BDD est, dans ce sens, fortement lié au fonctionnement de TDD ou l’on ne code que le minimum pour que le code de production fonctionne (Le refactoring permet d’être clair et d’obtenir le meilleur design, il n’y a pas de redondance, …). Le BDD est la couche « fonctionnelle », allant de pair avec le TDD.
Concrètement, ça ressemble à quoi ?
Concrètent, le BDD va se matérialiser dans un premier temps par une demande de l’expert métier, formalisée de cette manière :
En tant que <Utilisateur, Vendeur, Commercial, …>
Je souhaite <Pourvoir trier mes courriers, approvisionner mon stock, calculer mon bonus,…>
Pour < Gagner du temps, Satisfaire mes clients, Connaitre mon avancement, …>
Cette demande va mettre en place un contexte (En tant que …)
Puis va nous demander une fonctionnalité (Je souhaite …)
Et enfin nous explique quelle est la finalité de cette demande (Pour …)
Cela peut paraitre simple mais l’exercice ne l’est pas forcément ! Après un an de travail avec des experts métier et des consultants, l’exercice se révèle complexe car il nécessite …de connaitre son besoin (D’où l’intérêt de l’utiliser non ?) et de savoir correctement l’exprimer.
En complément de cette demande, l’expert métier va devoir réfléchir à des cas de test. De la même manière que lorsqu’il rédige un cahier de recette, il va pouvoir mettre à l’épreuve sa demande et détailler son besoin. On parle bien ici de besoin et non de solution. De la même manière que l’histoire utilisateur, les cas de test sont formalisés avec un langage composé de mot clés.
Ces mots clés forment un langage appelé Gherkin. C’est un langage compréhensible par tous, notamment par les experts métier (En savoir plus les Domain Specific Language).
De façon basique on va retrouver trois principaux mots clés
- Given (Je mets en place un contexte)
- When (Je procède à l’action, un événement, …)
- Then (Je vérifie)
Une histoire peut contenir évidement plusieurs cas de test et va nous permettre de tester le cas nominal (Tout se passe bien, c’est le cas le plus classique) …
Given Mon compte bancaire possède un crédit de 1000€
And mon compte épargne possède un crédit de 0€
When je procède à un virement de 500€ de mon compte bancaire vers mon compte épargne
Then Mon compte bancaire à un crédit de 500€
And mon compte épargne à un crédit de 500€
Les cas de test vont nous permettre également de tester les cas non passant
Given Mon compte bancaire possède un crédit de 1000€
When je procède à un virement de 1001€ vers mon compte épargne
Then Le virement est refusé pour cause de nom provision
Mais également les cas « à la marge »
Given Mon compte bancaire possède un crédit de 1000€
And La limite de virement est de 999€
When je procède à un virement de 999,01€ vers mon compte épargne
Then Le virement est refusé pour cause de montant trop élevé.
Vous avez pu remarquer que j’ai utilisé le mot clé « And« qui nous permet de ne faire qu’une seule action par ligne et de découper au maximum la fonctionnalité et le besoin. Je reviendrai plus en détail par la suite sur les mots clés et les possibilités offertes par le langage Guerkin.
Bon tout cela est très bien. Mais pourquoi avoir besoin d’insérer du fonctionnel dans mes tests et mon code ?
Pourquoi avoir besoin du BDD ?
Le père fondateur du BDD, Dan North, explique de manière très simple, comment est apparu le besoin de BDD. Lorsque l’on fait du TDD et que l’on doit développer une fonctionnalité plusieurs questions se posent :
- Que faut-il tester ?
- Que ne faut-il pas tester ?
- Par où commencer ?
Le TDD est, comme son nom l’indique, dirigé par les tests. C’est très bien mais le test possède une existence au sein d’une fonctionnalité. Et c’est là ou rentre en jeu le BDD qui va piloter notre façon de développer, par le besoin utilisateur. C’est l’expert métier, par son besoin et via les cas de test, qui va nous permettre de répondre aux questions des limites du test, des cas nominaux et des cas particuliers.
Là où réside toute la force mais aussi parfois la complexité du BDD c’est la fusion, avec le code, des demandes utilisateurs. Certains développeurs ont d’ailleurs le besoin, lorsqu’ils écrivent des tests unitaires, de séparer en trois étapes leur test unitaire (Le fameux pattern AAA).
[Test]
public void PeutTransfererDeLargentDunCompteVersUnAutre()
{
// Arrange
var compteBancaire = new CompteBancaire(1000);
var compteEpargne = new CompteEpargne(0);
// Act
Bank.Transfer(500).From(compteBancaire).To(compteEpargne);
// Assert
Assert.AreEqual(500, compteBancaire.Total);
Assert.AreEqual(500, compteEpargne.Total);
}
Arrange (Préparer), Act (Agir), Assert (Vérifier) … Ne voyez-vous pas une grande similitude avec notre langage Gherkin et son Given, When, Then ? Et bien ce test unitaire est, d’un point de vue purement technique, exactement ce que va exécuter notre test fonctionnel. BDD implique un langage naturel, compréhensible par tous qui va guider l’écriture de notre test.
Dans un premier temps, on intègre un fichier dit « Feature » contenant l’histoire et les cas de test.
Par exemple (Histoire1.feature)
Given Mon compte bancaire possède un crédit de 1000€
And Mon compte épargne possède un crédit de 0€
When Je procède à un virement de 500€ de mon compte bancaire vers mon compte épargne
Then Mon compte bancaire à un crédit de 500€
And Mon compte épargne à un crédit de 500€
Dans un second temps, le développeur procède à l’implémentation de ce cas de test.
Par exemple (Histoire1.cs)
[Given("Mon compte bancaire possède un crédit de 1000€")]
public MonCompteBancairePossedeUnCreditDe1000E()
{
_compteBancaire = new CompteBancaire(1000);
}
[Given("Mon compte épargne possède un crédit de 0€")]
public MonCompteBancairePossedeUnCreditDe000E()
{
_compteEpargne = new CompteEpargne(0);
}
[When("Je procède à un virement de 500€ de mon compte bancaire vers mon compte épargne")]
public JeProcedeAuVirementde500E()
{
Bank.Transfer(500).From(_compteBancaire).To(_compteEpargne);
}
[Then("Mon compte bancaire à un crédit de 500€")]
public JeProcedeAuVirementde500E()
{
Assert.AreEqual(500, _compteBancaire.Montant);
}
[Then("Mon compte épargne à un crédit de 500€")]
public JeProcedeAuVirementde500E()
{
Assert.AreEqual(500, _compteEpargne.Montant);
}
Lorsque le développeur va lancer les tests unitaires, le framework BDD (Ici Specflow) va lancer un unique test unitaire qui va jouer à la suite les méthodes Given, When et Then.
Un petit bilan ?
Quel bilan peut-on faire de cette manière de découper et tester les fonctionnalités d’un logiciel ?
Du point de vue Maitrise d’Ouvrage
- L’écriture de la demande utilisateur et notamment la contrainte « Pour » nécessite une réflexion approfondie sur la demande. Pourquoi ai-je besoin de cette fonctionnalité ? C’est souvent la contrainte du « Pour » qui va amener les experts métier et consultants fonctionnels à réfléchir à la réelle utilité d’une demande.
- L’écriture des cas des tests n’est pas aisée pour les experts métier ou les consultants ayant une habitude d’écrire des spécifications et non des cas de test… La maitrise d’œuvre doit en être consciente et aider à la rédaction de ces cas de test.
- Le langage Gherkin (Given, When, Then) ne doit pas être vu comme une contrainte mais une aide pour l’écriture de spécifications qui sont compréhensibles, simples et …testables ! (Ne jamais oublier le filtre INVEST). Au final, l’ensemble des histoires utilisateurs et leurs cas de tests associés forment les spécifications du projet avec des avantages intéressant : Cette documentation est testé en permanence est donc toujours à jour !
- Le BDD doit également être présenté, à mon avis, aux expert métiers comme une recette automatisée. C’est bien d’ailleurs ce que propose le BDD : D’automatiser les tests des spécifications. (Cela ne retire en rien la nécessité d’écrite de réels cahiers de test mais permet de produire avec certitude une application qui fera exactement ce que les spécifications demandent).
Du point de vue Maitrise D’œuvre
- Le BDD n’enlève en rien le besoin de test unitaire. Les tests effectués avec cette technique doivent être vus comme des tests de recette automatisé, proche des tests d’intégration. Ce sont des tests qui ne vont pas tester une méthode d’une couche mais bien une fonctionnalité. La différence est de taille.
- Le BDD fait gagner du temps. Certes, Il faut aider à la rédaction des cas de tests, Certes, il faut implémenter des tests supplémentaires, …Cela à un coût sur le temps de développement de l’histoire …Mais le gain de temps post développement est impressionnant (Coût d’une recette prolongé limité, d’un debug, d’une correction…).
- Et quel plaisir de ne pas avoir de régressions, quel plaisir de produire un logiciel qui fonctionne et qui répond aux attentes des utilisateurs …
Le BDD au quotidien
Techniquement, le BDD va impliquer plusieurs choses :
- Ce sont des tests automatisés qui doivent être lancés régulièrement et vont assurer la non régression des fonctionnalités de l’application.
- Les tests fonctionnels ne remplacent pas les tests unitaires.
- Les tests fonctionnels peuvent être situés à plusieurs endroits (IHM, Service, …).
- Le BDD nécessite une forte implication des développeurs et plus généralement de l’équipe de maitrise d’œuvre dans la compréhension du besoin et des demandes.
De nombreux outils vont permettent aux équipes de travailler sur les tests d’acceptance :
- JBehave (Le premier framework BDD crée par Dan North) (Java)
- RBehave – Rspec – Cucumber (Ruby)
- Specflow (.NET)
Ces outils vont nous permettre de faire le lien entre les cas de tests et les tests unitaires.
Voyons maintenant les subtilités et possibilité du langage Gherkin.
Ecrire une fonctionnalité (Feature)
Nota Bene : L’ensemble des outils permettant de faire du BDD fonctionnement de manière très similaire. Nos exemples sont tirés d’une utilisation avec l’outil Specflow (Pour .NET) mais vous n’aurez aucun mal à les utiliser avec un autre framework BDD.
La structure d’un fichier Feature
Le fichier que l’on appelle « Feature » est celui qui contient la demande utilisateur. Cette demande est structurée de cette manière :
Le mot clé Feature, suivi du titre de la demande
Feature : Transfert d’argent entre compte
Ensuite, la demande est structurée avec les termes « En tant que », « je souhaite », « Pour ».
En tant que Jean Dupond, client de la Banque BNP
Je souhaite pouvoir transférer de l’argent entre mes comptes
Pour gérer au mieux mon argent
Enfin, le fichier contient les N cas de tests proposés pour couvrir cette fonctionnalité.
Un cas de test commence par le mot clé « Scenario » suivi de son titre.
Scenario : Peut virer de l’argent entre un compte courant et un compte épargne
A noter qu’il est intéressant de structurer les titres des cas de test par les demandes :
- « Peut » dans le cas d’un test passant
- « Ne peut pas » dans le cas d’un test d’erreur.
Après le titre, on passe une ligne et l’on décrit le test avec les mots clés issu du langage Gherkin
Given Mon compte bancaire possède un crédit de 1000€
And mon compte épargne possède un crédit de 0€
When je procède à un virement de 500€ de mon compte bancaire vers mon compte épargne Then Mon compte bancaire à un crédit de 500€
Exemple complet d’un fichier feature :

Exemple d'un fichier Feature
Les mots clés Given, When, Then, And, But
Revenons un peu plus en détail (et en exemple !) sur les différents mots clés possible pour décrire un cas de test utilisateur.
A noter qu’il est tout à fait possible de traduire les mots clés Given, When et Then dans la langue des spécifications. Plus de 40 langues sont supportées.
Given (Que l’on pourrait traduire en français par « Étant donné ») permet de mettre en place un contexte, des préconditions. On va déclarer ici les différents éléments qui vont nous permettre de contextualiser notre test et de permettre à ce dernier de exécuter.
En terme fonctionnel
On va décrire les prérequis pour que le test puisse s’effectuer.
- Given Mon compte bancaire possède 1000€
- And Mon compte épargne possède 0€.
On décrit bien deux choses : La possession de deux comptes différents. Un bancaire, un épargne. Et le montant de départ dans ces deux comptes.
Du point de vue technique
- Le Given va nous permettre de créer les objets utilisés par le cas de test. Dans notre cas du compte bancaire, une objet client qui a deux comptes de type différent, avec chacun un montant….
Par exemple
[Given("Mon compte bancaire possède un crédit de 1000€")]
public MonCompteBancairePossedeUnCreditDe1000E()
{
_compteBancaire = new CompteBancaire(1000);
_client.Comptes.Add(_compteBancaire);
}
When (Que l’on traduit par « Quand ») permet de décrire une action, un événement.
En terme fonctionnel
On va pouvoir décrire, en fonction du besoin, différentes choses :
- Une interaction sur le système (Clic sur un bouton, un lien, …)
- Le déclenchement d’un événement (Batch, Système, …)
- Une action générale sur un concept métier.
C’est cette dernière action qui est utilisée dans l’exemple du transfert d’argent
- When je procède à un virement de 1001€ de mon compte bancaire vers mon compte épargne
On ne décrit pas ici une interaction précise sur un site internet ou un système informatique mais bien une action métier qui consiste à transférer de l’argent d’un compte à un autre.
Du point de vue technique
- On va ici effectuer une action sur notre contexte, notre objet. Par exemple, persister des éléments dans la base de données, lancer un évènement, un clic … ou dans notre exemple appeler un service qui gère le transfert d’argent entre deux comptes.
[When("je procède à un virement de 1001€ de mon compte bancaire vers mon compte épargne")]
public JeProcedeAuVirementde1001E()
{
Bank.Account(_client).Transfer(1001).From(_compteBancaire).To(_compteEpargne);
}
Then (Que l’on peut traduire par « Alors ») décrit une observation concernant l’état final du système, d’un objet ou d’un concept métier.
Du point de vue fonctionnel
On va pouvoir vérifier
- L’état d’un processus métier après l’action du When
- La levée d’un message d’erreur
- Un calcul, une transformation, …
Dans le cas de notre exemple, le Then va tester la levée d’une erreur métier
- Then Le système renvoi le message d’erreur Impossible d’effectuer le virement pour cause de manque de provision
Du point de vue technique
- On va effectuer nos assertions et vérifier, conformément à la demande utilisateur, que les montants dans les deux comptes bancaires sont corrects. On va également vérifier qu’il n’y a pas eu de message d’erreur.
[Then("Le système renvoi le message d'erreur Impossible d'effectuer le virement pour cause de manque de provision")]
public JeProcedeAuVirementde500E()
{
Assert.IsNotNull(_exception);
Assert.AreEqual("Impossible d'effectuer le virement pour cause de manque de provision",_exception.Message);
}
Allons un peu plus loin …pour que le BDD soit un plaisir !
Après plus d’un an de développement avec le BDD, plusieurs facteurs permettent d’utiliser le BDD de façon à ne pas être une méthode contraignante. En effet, sans certaines règles, le BDD peut se révéler long, complexe et le risque d’abandon de la méthode peut être important sans un certain nombre de conseil :
Les paramètres
Lors du passage à l’écriture des tests unitaires, les développeurs ont la possibilité d’utiliser des expressions régulières pour transformer certain éléments des Given, When, Then en paramètre. Cette fonction est essentielle pour pouvoir factoriser un maximum les tests unitaires.
Prenons l’exemple du cas classique d’implémentation d’une calculatrice …
Scenario : Peut additionner deux chiffres
Given L’utilisateur saisi le chiffre 3 sur la calculatrice
And L’utilisateur appuie sur la touche +
And L’utilisateur saisie le chiffre 4 sur la calculatrice
When L’utilisateur appuie sur la touche =
Then Le résultat est 7
Scenario : Peut soustraire deux chiffres
Given L’utilisateur saisi le chiffre 6 sur la calculatrice
And L’utilisateur appuie sur la touche -
And L’utilisateur saisie le chiffre 9 sur la calculatrice
When L’utilisateur appuie sur la touche =
Then Le résultat est -3
… Au final voila ce que donne le fichier Feature complet.

Feature : Calculatrice
Nous avons ici quatre cas de test, sans utilisation de paramètre, nous avons en tout 12 méthodes Given (Un Given et deux And * 4) à implémenter, 4 méthodes When et enfin 4 méthodes Then….
Ce que nous remarquons c’est la similitude des différents cas de test mais aussi des phrases entre les Given, When et Then. Ainsi, avec l’utilisation de paramètres lors de l’implémentation de nos méthodes, nous n’avons plus que trois phrases différentes
- L’utilisateur saisi le chiffre X sur la calculatrice
- L’utilisateur appuie sur la touche X
- Le résultat est X
Concrètement, lors de l’utilisation des balises Given, When et Then lors de l’écriture des tests unitaires, nous allons transformer la variable X de nos phrases en expression régulière.
L’implémentation des méthodes (Fichier .cs) donne cela :
[Given("L’utilisateur saisi le chiffre (.*) sur la calculatrice")]
public SaisieUnChiffre(int chiffre)
{
_calculatrice.AjouterChiffre(chiffre);
}
[Given("L’utilisateur appuie sur la touche (.*)")]
public AppuieSurLaTouche(string touche)
{
_calculatrice.AppuyerSurTouche(touche);
}
[When("Le résultat est (.*)")]
public LeResultatEst(string resultat)
{
Assert.AreEqual(resultat,_calculatrice.Resultat());
}
Les framework BDD sont assez souple et permettent de transformer les paramètres en variables typés :
Ainsi
Given Un chient pesant 45,2Kg, mesurant 120cm et née le 20/12/2001
Peut se transformer en
[Given("Un chient pesant (.*)Kg, mesurant (.*)cm et née le (.*))]
GivenInitialisationDuChient(decimal poids, int taille, DateTime dateNaissance)
{
}
On récupère ainsi le poids, la taille et la date de naissance du chien … Facile non ?
Mutualiser les Given / When / Then
Les experts métiers doivent être conscient, tout comme les développeurs, que l’essence du BDD se trouve dans du texte écrit dans un langage naturel. Ce texte est réellement ce qui va être exécuté. Il est donc primordial que les Given, When et Then soit factorisés.
Imaginons deux cas de test
Given le stock pour le produit X est à 5
When Un client achète le produit X
Then le stock pour le produit X est à 4
Et
Given le stock du magasin pour le produit Y est égal à 5
When Un client du magasin achète un produit Y
Then le stock du magasin pour le produit Y est égal 4
Ces deux cas de test expriment exactement le même besoin (Pouvoir gérer le stock en fonction de l’achat d’un produit par un client). Pourtant, les Given, When et Then ne sont pas exprimés exactement de la même manière. Le langage Gherkin permet la gestion de paramètre, ainsi, il n’y a aucun problème pour mutualiser le fait que le cas de test 1 utilise le produit X et le cas de test 2 le produit Y. Ce qui est problématique ici, c’est la manière de décrire le besoin.
Prenons ces deux Given qui expriment exactement le même besoin :
- Given le stock pour le produit X est à 5.
- Given le stock du magasin pour le produit Y est égal à 5.
Avec l’utilisation des paramètres, il est aisée de transformer le produit X et Y en paramètre. Toutefois un problème persiste : La description du besoin diffère légèrement.
Deux options sont ici possibles :
1 – Homogénéiser l’expression du besoin et transformer les cas de test pour obtenir cela :
- Given le stock pour le produit X est égal à 5.
- Given le stock du magasin pour le produit Y est égal à 5.
2 – Laisser les deux expressions tel quel et appliquer le même Given à la même méthode lors de l’implémentation
[Given("le stock pour le produit (.*) est à (.*)")]
[Given("le stock du magasin pour le produit (.*) est à (.*)")]
public void GivenGestionDuStock(string produit, int stock)
{
}
Je vous conseille fortement l’utilisation de la première méthode qui vous permet d’obtenir une cohérence dans l’écriture des spécifications et qui évite les doublons lors de l’implémentation des fichiers feature.
Les jeux de données / Tableaux
L’une des plus-values apportée par les frameworks BDD est l’incorporation des jeux de données dans les cas de test. Les experts métier vont ainsi pouvoir décrire leur cas de test avec des données réelles.
Le premier cas de test ci-dessous correspond à un cas de test classique, utilisant la bonne pratique de mutualisation des Given / When et Then. On remarque qu’il est plutôt lisible mais ont peut toutefois avoir des difficultés pour le lire et comprendre rapidement l’état des différents comptes bancaires au cours du cas de test.

Cas de test sans jeux de données
Essayons de transformer le précédent cas de test pour utiliser les tableaux.

Cas de test avec utilisation des tableaux
On peut remarquer que le cas de test décrit exactement la même chose. Pour présenter les données du test, on utilise les tableaux. Ces derniers sont simplement composés d’une entête sur la première ligne suivi des N lignes composant le tableau. Le séparateur est ici un « pipe ».
Les avantages lors de l’utilisation des tableaux sont nombreux :
- Le cas de test est plus lisible. Specflow mettant ici en valeur via une coloration syntaxique les données
- La mise en forme est automatique.
- On voit très rapidement comment les données ont évolués au cours du cas de test
- L’ajout d’un ligne dans un tableau est extrêmement simple
- Les experts métiers peuvent manipuler plus simplement les données et tester de nouveaux cas de test très simplement.
- Pour le développeur, l’implémentation des Given, When, Then va être simplifiée.
Si l’expert métier souhaite effectuer un cas de test supplémentaire …

Nouveau cas de test ... sans coût de développement !
… Et bien ce cas de test ne coûtera rien au développeur ! L’ensemble des Given, When et Then sont déjà implémentés dans le code et seul de nouvelles données sont injectées ! Pratique, rapide et extrêmement intéressant pour tester rapidement et facilement une fonctionnalité.
Le mot clé Background
Ce mot clé permet de factoriser une nouvelle fois les demandes utilisateur et plus particulièrement la mise en place d’un contexte.
En effet, lors de l’écriture d’une demande utilisateur, le plus souvent, les différents cas de test ont le même prérequis et/ou contexte. Le mot clé va Background donc permettre de mettre en place un contexte commun à l’ensemble des cas de test.

Utilisation du mot clé Background
Dans cet exemple, le Background nous permet d’éviter la redondance d’information et de mettre en place un contexte commun à l’ensemble des cas de test de la demande. Le mot clé Background permet évidement de mettre en place des scénarios bien plus complexes et complets avant l’exécution de chacun des cas de test.
Du point de vue du développeur, le mot clé Background n’a aucune incidence sur le développement. Pour l’exemple, le Background correspond à une méthode avec l’entête Given.
[Given("Le stock de produit (.*) est de (.*)")]
public void LeStockDeProduitEstDe(string produit, int stock)
{
_GestionnaireProduit(produit).Stock(stock);
}
Les Scénarios multiples
C’est une des fonctionnalités la plus productive et intéressante du BDD. En effet, comme pour les tests unitaires que les développeurs écrivent pour couvrir toutes les possibilités et s’assurer d’une couverture unitaire complète, la couverture fonctionnelle décrite via les Feature se doit également de couvrir entièrement une fonctionnalité.
Imaginons que nous avons une règle métier simple : Pour les comptes bancaire, les PEL, les PEE et les CEL, nous devons appliquer une règle métier : Le quota maximum pour un virement est de 5000€.
Se pose ici la question de l’écriture des cas de test. Doit-on écrire autant de cas de cas test que de possibilités ? La réponse est oui. Pour une couverture complète, il faut tester la règle avec l’ensemble des comptes. On a donc les scénarios suivants :
- Ne peut pas effectuer un virement de plus de 5000€ sur un PEL
- Ne peut pas effectuer un virement de plus de 5000€ sur un PEE
- Ne peut pas effectuer un virement de plus de 5000€ sur un CEL
Les experts métiers ont souvent peu de temps à consacrer à l’écriture des cas de test (C’est d’ailleurs très dommage …) mais il faut faire avec (Nous en reparlerons dans quelques paragraphes) mais BDD propose une solution élégante et parfaitement adaptée à ce genre de scénario.
En anglais le terme est « scenario outline » et va vous permettre de rejouer, avec un tableau de valeur possible, plusieurs fois un même scénario.
Ainsi, les 3 scénarios décrits au-dessus vont pouvoir être transformés de cette manière

Exemple d'utilisation du Scenario Outline
Lorsque vous allez lancer les tests unitaires, l’outil BDD va lancer 3 tests unitaires en remplaçant le terme <compte> par la valeur définie dans le tableau « Examples ». Simple et efficace !
Vous pouvez ainsi imaginer des scénarios plus complexes combinant Scenario Oultline et tableau de données.

Scenario Ouline et Tableaux de données
Dans cette feature, nous avons un cas de test qui va être exécuté 3 fois (Il y a 3 lignes dans le tableau Examples). Les colonnes de ce tableau vont pouvoir être utilisés dans les tableaux de données (Utilisé ici dans le When).
Voyons en détail ce qui est exécuté à chaque fois :

Sans Scenario Outline ...
Partager des données entre les spécifications
Il y a une chose qui est parfois complexe à comprendre avec le BDD, c’est que la liaison entre les phrases écrites dans le fichier feature sont fortement liées à l’entête du test unitaires.
[Given("Mon compte bancaire possède un crédit de 1000€")]
Ainsi, vous aller pouvoir facilement partager les Given, When et Then entre vos différentes fonctionnalités. Un exemple simple est celui des exceptions métier.
Dans vos différentes feature, vous aller écrire des cas de test non passant qui se finiront par une levée d’erreur. Classique non ? L’un des avantage du BDD et le contexte d’exécution qui va lier les différentes méthodes Given, When et Then.
Imaginons une première feature …

Et une seconde …

Vous remarquez la similitude des deux Then qui décrivent l’erreur ? Si vous implémentez ces deux cas de test sans factorisation, voila ce que vous devriez avoir :
Feature.cs 1
[Binding]
public class FeatureConnexionSecurise
{
[Given("Utilisateur qui entre les informations suivant dans l'espace de connection")]
public void UtilisateurEntreInformationsDansEspaceConnection(Table infosConnection)
{
}
[When("il effectue la demande de connection")]
public void IlEffectueLaDemandeDeConnection()
{
}
[Then("Une erreur apparait : \"(.*)\"")]
public void UneErreurApparait(string erreur)
{
}
}
Feature.cs 2
[Binding]
public class FeatureBonDeCommande
{
[Given("Utilisateur qui rempli le bon de commande suivant")]
public void UtilisateurQuiRempliLeBonDeCommande(Table bonDeCommande)
{
}
[When("il valide le bon de commande")]
public void IlValideLeBonDeCommande()
{
}
[Then("Une erreur apparait : \"(.*)\"")]
public void UneErreurApparait(string erreur)
{
}
}
Si vous lancez les tests unitaires, Specflow va vous répondre qu’il a trouvé deux « Then » identiques.

Erreur d'exécution des tests Specflow
La solution est ici de factoriser au maximum vos méthodes et que les deux cas de test partagent la même méthode Then.
Et la vous devriez vous poser la même question : « D’accord mais comment nos deux cas de test vont pouvoir « transmettre » leur données à la méthode Then factorisé ?« . Plus concrétement, comment les exceptions levées dans les méthodes When vont être envoyés à l’unique méthode Then ?
La réponse se trouve dans les contextes de données proposés par Specflow. Pour vous permettre de mutualiser vos Given, When et Then, vous allez pouvoir faire transiter vos objet entre les différentes méthode (Given, When ou Then).
Différentes solutions sont proposés pour faire transiter les données entre vos méthodes Given, When, Then. Je vais en présenter quatre mais attention, toutes ne se valent pas !
Le plus simple, les variables d’instances
C’est souvent par manque de temps ou de compréhension du fonctionnement du BDD que l’on utilise, à juste titre, les variables d’instance pour faire transiter nos données.
On a besoin d’un objet compte bancaire crée dans le Given, pour l’utiliser dans le When ? qu’a cela ne tienne, mettons le en variable d’instance ! C’est effectivement une solution simple mais qui va montrer rapidement ses limites lorsque l’on veut mutualiser un maximum les Given, When, then au sein de l’ensemble des spécifications de l’application.
private BonDeCommande _bonDeCommande;
[Given("Utilisateur qui rempli le bon de commande suivant")]
public void UtilisateurQuiRempliLeBonDeCommande(Table bonDeCommande)
{
// Assigner la variable d'instance
_bonDeCommande = bonDeCommande.CreateInstance<BonDeCommande>();
}
[When("il valide le bon de commande")]
public void IlValideLeBonDeCommande()
{
// Réutiliser la variable d'instance
_bonDeCommande.Valider();
}
Si maintenant, nous décidons de créer une nouvelle spécification, nous ne pouvons pas facilement réutiliser notre Given et When car ils utilisent une variable d’instance et sont donc liée à cette classe.
Le plus risqué, les variables statiques
L’une des tentatives possible pour transmettre des données entre vos spécifications peut être dans l’ajout de variables statiques. Dans le cas de notre exemple de bon de commande, on peut réussir à le faire fonctionner en passant la variable bon de commande statique … toutefois je vous déconseille de faire cela car ce fonctionnement peut poser des problèmes de synchronisation lors de l’exécution des tests et est déconseillé par Specflow.
Simple et efficace : Le contexte d’exécution Specflow
En utilisant l’objet ScenarioContext qui est transverse lors de l’exécution d’un scénario ou l’objet FeatureContext, transverse lors de l’exécution de toute la feature, tout devient plus simple pour transmettre vos données.
Dans nos deux cas de test, les méthodes When vont lever des exceptions. Cette exception va être ajoutée au contexte du test.
[When("il valide le bon de commande")]
public void IlValideLeBonDeCommande()
{
try
{
ValiderLeBonDeCommande();
}
catch (Exception ex)
{
ScenarioContext.Current.Set(ex);
}
}
[When("il effectue la demande de connection")]
public void IlEffectueLaDemandeDeConnection()
{
try
{
TenterDeSeConnecter();
}
catch (Exception ex)
{
ScenarioContext.Current.Set(ex);
}
}
La méthode Then va tester
- S’il y a une exception présente dans le contexte
- Si cette exception contient le bon message (Celui défini par la spécification)
[Then("Une erreur apparait : \"(.*)\"")]
public void UneErreurApparait(string erreur)
{
Assert.IsNotNull(ScenarioContext.Current.Get<Exception>());
Assert.AreEqual(erreur, ScenarioContext.Current.Get<Exception>().Message);
}
Le contexte est un outil qui va vous permettre de réaliser des implémentations propres de vos features et va grandement vous aider à mutualiser les comportements.
Le plus intéressant : L’injection du contexte
C’est, à mon avis, la méthode la plus intéressante. Elle reprend les avantages du point évoqué au dessus (Les ScenarioContext et FeatureContext) tout en utilisant des contextes fortement typés et donc bien plus intéressant pour l’implémentation de vos tests fonctionnels. Specflow permet d’injecter automatiquement les contextes nécessaires au bon fonctionnement de vos spécifications grâce au principe d’inversion de contrôle.
Prenons cet exemple de Feature
Feature: Calcul du panier Background: Given Le catalogue du magasin est | Nom | Prix | Stock | | Chocolat Blanc | 2.5 | 10 | | Chocolat Noir | 2 | 8 | | Chocolat Noisette | 3.25856 | 120 | Scenario Outline: Peut calculer le total du panier avec un seul produit Given Morgan Leroi, client du site When Le client ajoute les articles au panier | Produit | Quantite | | <Produit> | <Quantite> | And Le client valide le panier Then le montant total du panier est <Total>€ Examples: | Produit | Quantite | Total | | Chocolat Blanc | 3 | 7.5 | | Chocolat Noir | 5 | 10 | Scenario: Peut calculer le total du panier avec plusieurs produits Given Morgan Leroi, client du site When Le client ajoute les articles au panier | Produit | Quantite | | Chocolat Noir | 5 | | Chocolat Blanc | 3 | And Le client valide le panier Then le montant total du panier est 17.5€ Scenario: Peut calculer le total du panier avec l'arrondi Given Morgan Leroi, client du site When Le client ajoute les articles au panier | Produit | Quantite | | Chocolat Noisette | 5 | And Le client valide le panier Then le montant total du panier est 16.29€ Scenario: Ne peut pas calculer un panier vide Given Morgan Leroi, client du site When Le client ajoute les articles au panier | Produit | Quantite | | | | And Le client valide le panier Then Un message d'erreur apparait : "Votre panier est vide"
On a besoin de créer et faire transiter entre les Given, When et Then l’objet Magasin contenant le stock et les produits ainsi que l’objet Client. Au lieu de les placer dans l’objet ScenarioContext de Specflow, on va créer un objet fortement typé pour nos spécifications.
public class ContexteMagasin
{
public Magasin Magasin { get; set; }
public Client Client { get; set; }
}
Pour injecter ce contexte lors de l’execution des spécifications, il suffit de créer une dépendance à ce contexte dans la classe qui implémente les méthodes Given, When et Then, en les référençant dans le constructeur.
[Binding]
public class CasDeTest
{
private readonly ContexteMagasin _contexte;
private readonly ContexteCommun _contexteCommun;
public CasDeTest(ContexteMagasin contexte, ContexteCommun contexteCommun)
{
_contexte = contexte;
_contexteCommun = contexteCommun;
}
[Given(@"Le catalogue du magasin est")]
public void GivenLeStockDuMagasinEst(Table table)
{
_contexte.Magasin = new Magasin { Stock = table.CreateSet<Produit>() };
}
[Given(@"(.*) (.*), client du site")]
public void GivenMorganLeroiClientDuSite(string prenom, string nom)
{
_contexte.Client = new Client(prenom, nom);
}
[When(@"Le client ajoute les articles au panier")]
public void WhenLeClientAjouteLesArticlesAuPanier(Table table)
{
_contexte.Client.Panier = RemplirUnPanier(table);
}
[When(@"Le client valide le panier")]
public void WhenLeClientValideLePanier()
{
try
{
_contexte.Client.Panier.ValiderLePanier();
}
catch (Exception ex)
{
_contexteCommun.Exception = ex;
}
}
Lors de l’exécution des tests, Specflow va s’occuper d’instancier ces objets et les transmettre aux différentes classes ayant ces même dépendances. N’hésitez pas à regarder l’exemple complet disponible sur CodePlex.
Un exemple complet …?!
Après avoir vu un certain nombre d’exemple et d’astuces pour faire du BDD votre allié, je vous propose un exemple complet reprenant l’ensemble des points vus dans cet article.
Télécharger les sources du projet d’exemple
Pour aller plus loin …
- La documentation Specflow et Cucumber avec de nombreux exemples.
- Un des livres référence sur le BDD

public MonCompteBancairePossedeUnCreditDe1000E()
{
_compteBancaire = new CompteBancaire(1000);
_client.Comptes.Add(_compteBancaire);
}




A really quick post just to share a 





Unit testing, done right, can mean the difference between a failed project and a successful one, between a maintainable code base and a code base that no one dares touch, and between getting home at 2 AM or getting home in time for dinner, even before a release deadline.






For example, if you already developped a classic website, you can easily develop the Mobile version with a new view called by the same name and with the extension .mobile.cshtml.

Last week, Microsoft has presented Windows 8 during the BUILD event,.








