Accueil Nos publications Blog Introduction au Behavior Driven Development

Introduction au Behavior Driven Development

Le Behavior Driven Development (BDD) est un concept défini en 2003 par Dan North. Il s’agit d’une continuité naturelle du Test Driven Development (TDD) dans le sens où il ne s’agit finalement que d’une formalisation d’un certain nombre de concepts auxquels aboutissent souvent les développeurs ayant acquis une bonne expérience de TDD.

Le BDD vise principalement à simplifier la compréhension de la finalité du TDD et à rendre plus cohérent les tests dans un environnement Agile où les experts métiers ont un rôle prépondérant.

Les prémisses, ou pourquoi il est si compliqué d’expliquer le TDD aux autres

En tant que personne pratiquant le TDD depuis quelques temps, vous avez sans doute eu à faire à ce cas de figure : comment expliquer le TDD à quelqu’un qui ne connait pas le principe et qui souvent débute en développement ? Et comment vous l’a-t-on expliqué à vous pour que vous trouviez ça si génial et que vous cherchiez à propager l’idée ?

Le constat de Dan North, et ce que j’ai pu voir autour de moi confirme ce point, est qu’il est extrêmement compliqué de faire comprendre la philosophie derrière le TDD à un néophyte. A peine le concept présenté surgissent des tas de questions :

  • Ça sert à quoi d’écrire les tests en premier ?
  • Ok, j’écris les tests en premier mais quel est le premier test que j’écris ?
  • Je dois tout tester ou seulement certaines choses ?

S’en suivent des réponses pas toujours très éclairantes, principalement basées sur l’expérience personnelle, et une phase intermédiaire où notre interlocuteur à compris que c’était probablement génial et essaie de son coté en se fabriquant ses propres règles au fil de l’eau.

Dan North a ainsi défini un profil type d’adoption de TDD :

  1. Le développeur continue d’écrire son code et fait des tests autour de celui-ci
  2. Au fur et à mesure que le nombre de tests augmentent, la confiance du développeur fait de même
  3. A un moment (souvent via quelqu’un d’autre), le développeur réalise qu’écrire le test avant le code permet de se concentrer plus facilement sur le code
  4. Lorsqu’il revient sur un code testé, le développeur se rend compte que celui-ci sert également de documentation pour se rappeler comment tout fonctionne
  5. Le développeur commence a se mettre dans la peau de la personne utilisant son code et le conçoit pour cette personne. Le TDD fait partie intégrante de la conception
  6. Suit en général le constat que TDD n’est pas que simplement du test mais permet de définir le comportement du code
  7. Le comportement défini des interactions entre objets et donc l’utilisation de Mocks devient indispensable

La progression jusqu’au point 4 se fait assez naturellement, cependant les points suivants posent beaucoup plus de problèmes. Le but du BDD est de casser ce schéma en intégrant dès le début les problématiques de comportement au centre de la philosophie de développement.

Ainsi, on ne teste plus du code, on valide que le comportement d’une fonctionnalité est conforme au contrat initial.

Il s’agit au final de la même chose dite différemment mais cette approche est particulièrement intéressante pour plusieurs raisons :

  • Elle est beaucoup plus simple à comprendre pour un néophyte
  • Nous sommes sorti du vocabulaire de développement et pouvons intégrer dans le processus de test d’autres personnes
  • Elle va permettre de se concentrer sur ce qui est vraiment primordial au niveau fonctionnel

Poser les bonnes questions aux bonnes personnes avant de faire son test

Le but du TDD est de faire en sorte que le développeur se pose les bonnes questions avant de développer son code.

Le but du BDD est de faire en sorte que ce ne soit pas le développeur seul dans son coin qui se pose ces questions.

En effet le BDD est particulièrement ancré dans la méthodologie Agile et met en  avant la participation des utilisateurs métiers et des QAs qui sont les mieux placés pour savoir ce que doit vraiment faire notre code et surtout dans quel but.

Se dégage ainsi une symbiose plus naturelle entre la définition de la user story (ou d’un cas d’utilisation) et la rédaction des tests. Symbiose qui se fait au final également avec le TDD pur mais qui n’est pas facile à appréhender pour un débutant et qui en général n’implique que l’équipe technique.

La définition des user stories est en général déjà drivée par le comportement. Il est important de comprendre qui à besoin de quoi, pourquoi et ce qui est plus important que le reste.

Le BDD reprend ces principes afin de découper une story en scénarii concrets en se concentrant  sur le comportement :

Quand j'ai [conditions initiales]
et que je fais [action]
alors j'obtiens [résultat]

Et soudainement, les réponses aux questions posées par notre utilisateur initial sont très claires :

  • Ça sert à quoi d’écrire les tests en premier ? On n’écrit pas des tests mais on définit le comportement fonctionnel de notre code
  • Ok, j’écris les tests en premier mais quel est le premier test que j’écris ? Les scénarii sont implémentés par ordre d’importance
  • Je dois tout tester où seulement certaines choses ? On ne test pas, on vérifie que notre code est conforme à la story métier, la liste des choses à tester est définie par les scenarii.

Un vocabulaire unique pour les gouverner tous et, dans les ténèbres, les lier

Nous arrivons dans la dernière étape du BDD, probablement l’évolution la plus significative par rapport au TDD : la mise en place d’un vocabulaire unifié et généralisé pour décrire ce que doit faire notre application.

Un des travers que tente de réparer l’agilité est le manque de communication entre les différents participants d’un projet. Pire, dans la plupart des cas, ils emploient des vocabulaires différents pour parler de la même chose.

Nous avons vu dans la rubrique précédente que le comportement doit être défini en collaboration étroite avec les utilisateurs métier et la QA afin qu’il ait du sens. Ces personnes ne sont en général pas capable d’écrire des tests unitaires en Java, .Net ou autre, par contre il n’ont aucun problème à définir leur besoin dans les termes que nous avons vu juste avant :

Quand j'ai [conditions initiales]
et que je fais [action]
alors j'obtiens [résultat]

Une fois tous nos scénarii écrits dans ce langage, il suffit de les transformer en tests en utilisant soit en les transcrivant un par un soit en utilisant des frameworks de BDD tels que JBehave ou NBehave.

Nous avons donc tout un cycle de développement partant de la définition de la user story jusqu’à ses tests de validation, défini dans un langage commun à tous les interlocuteurs, compris et validés par tous et ce dès le début. Et comme tout est centralisé dans nos scénarii, si un scénario évolue il y a très peu d’impact sur le cycle de développement ce qui renforce l’aspect Agile du projet.

Un exemple pour illustrer tout ca

Reprenons un exemple simple et modélisons un transfert d’argent entre deux comptes bancaires.

Le scénario le plus évident et donc important est :

Quand j'ai un compte A avec 10€
Et un compte B avec 0€
Si je transferts 5€ de A vers B
Alors j'obtiens un compte A avec 5€
Et un compte B avec 5€

Un autre scénario possible :

Quand j'ai un compte A avec 10€
Et un compte B avec 0€
Si je transferts 15€ de A vers B
Alors j'obtiens une erreur m'interdisant le transfert

etc.

Chaque scénario donnera un test, ou une série de tests regroupés selon les préférences.

Et traduisons le en code

Maintenant que nous avons définis une liste de scénarii, nous allons voir ce que ça donne au niveau code avec JBehave. Le but de cette partie n’est pas de rentrer dans les détails de JBehave. Je vous invite à consulter le site officiel afin de voir comment le configurer et connaitre plus en détail toutes ses possibilités.

Note : Par défaut la syntaxe JBehave est en anglais mais il est possible de le configurer afin qu’elle soit en français ou dans une autre langue.

Notre fichier contenant nos scenarii (account_scenario.story) :


Scenario: Move Money from accounts when I have enough money

Given an account with 10 euros
And another with 0 euros
When I transfer 5 euros
Then the first account should have 5 euros
And the second one 5 euros

Scenario: Move Money from accounts without enough money

Given an account with 5 euros
And another with 0 euros
When I transfer 10 euros
Then the first account should have 5 euros
And the second one 0 euros
And I should see an error

Et le code java associé :


public class AccountStory extends Embedder {

private Account accountA;
private Account accountB;
private boolean errorOccured;

@Given("an account with $amount euros")
public void fillAcountA(int amount) {
accountA = new Account(amount);
}

@Given("another with $amount  euros")
public void fillAcountB(int amount) {
accountB = new Account(amount);
}

@When("I transfer $amount euros")
public void transfertMoney(int amount) {
errorOccured = accountA.transfertTo(accountB,amount);
}

@Then("the first account should have $amount euros")
public void checkAccountA(int amount) {
Assert.assertEquals(amount, accountA.getAmount());
}
@Then("the second one $amount euros")
public void checkAccountB(int amount) {
Assert.assertEquals(amount,accountB.getAmount());
}

@Then("I should see an error")
public void checkErrorRaised() {
Assert.assertFalse(errorOccured);
}

}

Comme vous pouvez le voir la correspondance entre les deux se fait très simplement via des annotations Java. Des expressions régulières permettent d’extraire les paramètres et des les injecter dans les méthodes de test.

Il ne nous manque plus que la possibilité de lancer les tests. Cela se fait via une classe qui implémente l’interface Embeddable. Pour notre exemple nous allons utiliser le launcher JUnit, ce qui nous donne :


public class AccountScenario extends JUnitStory {

@Override
public Configuration configuration() {
URL storyURL = null;
try {
// This requires you to start Maven from the project directory
storyURL = new URL("file://" + System.getProperty("user.dir")
+ "/src/main/stories/");
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new MostUsefulConfiguration().useStoryLoader(
new LoadFromRelativeFile(storyURL)).useStoryReporterBuilder(
new StoryReporterBuilder().withFormats(Format.HTML));
}

public List candidateSteps() {
return new InstanceStepsFactory(configuration(), new AccountStory())
.createCandidateSteps();
}

@Override
@Test
public void run() {
try {
super.run();
} catch (Throwable e) {
e.printStackTrace();
}
}
}

Et enfin notre classe Account qui vérifie les tests :


public class Account {
private int amount = 0;

public Account(int amount) {
this.amount = amount;
}

public boolean transfertTo(Account accountB, int amount) {
if(this.amount >= amount) {
this.amount -= amount;
accountB.addAmount(amount);
return true;
}
return false;
}

public int getAmount() {
return amount;
}

void addAmount(int amount) {
this.amount += amount;
}
}

Vous pouvez trouver le code source du projet maven ici.

Conclusion

Le behavior Driven Development n’est pas une révolution en soit, il s’agit plus à mon sens, de la normalisation de ce vers quoi tend naturellement le Test Driven Development afin de le rendre plus accessible aux personnes ne le connaissant pas et surtout pour l’adapter afin qu’il fasse partie intégrante d’un process Agile.

Il s’agit surtout de se poser les bonnes questions, avec les bonnes personnes et de s’assurer que tout le monde communique dans le même langage, et que ce langage soit le plus naturel possible afin que tout le monde se comprenne bien.

Il a aussi l’avantage de mieux cadrer les tests, puisque les scénarii sont des cas concrets, et d’inciter davantage à utiliser des mocks plutôt que de transformer les tests en tests d’intégrations.

Aller plus loin

Le site de référence du BDD : https://behaviour-driven.org

Un article de Dan North décrivant la genèse du BDD : https://dannorth.net/introducing-bdd/

Les articles wikipedia en Français et en Anglais (beaucoup plus complet)

Le site de JBehave.