Je ne vais pas ici développer le BDD en long et en large. Il y a de très bon sites pour cela, http://behaviour-driven.org/ entre autres.

Un développeur qui commence à pratiquer le TDD y trouve rapidement un bénéfice : sa confiance dans le code ne cesse de grandir car il met en place des centaines de garde-fous qui, à tout instant, le remettent dans le droit chemin.
Dans un 2ème temps, au delà de cet aspect de contrôle des régressions, un développeur va trouver dans le TDD un bon moyen pour documenter son code : les tests sont alors des exemples d'utilisation. Mais ce n'est que lorsqu'on approche le TDD comme technique de conception que l'on peut en mesurer toute la puissance.

Le BDD est l'évolution naturelle du TDD. Le pilotage par les tests permet littéralement de découvrir les interfaces du code à écrire. Et c'est là que le BDD entre en action en proposant un canevas : "étant donné un contexte, si un évènement survient, alors le système vérifie un certain nombre de conditions".

Prenons un exemple. Une fonctionnalité de mon système consiste à fournir le transfert d'argent d'un compte épargne vers un compte courant.
Pour le tester, je peux donc décrire un contexte :

  • Si le compte épargne est en crédit de 100
  • Et le compte courant est en crédit de 10

Puis initier un évènement :

  • Quand je transfère 20 vers le compte courant

Et enfin vérifier le nouvel état du système :

  • Le solde du compte épargne est 80
  • Et le solde du compte courant est 30

Un test facilement lisible va respecter ces étapes. Par exemple, en C# avec NUnit, je peux décrire mon contexte initial sous la forme d'un héritage de classes (une classe par condition initiale) :

namespace SiLeCompteEpargneEstEnCreditDe100 {
  public class C_SiLeCompteEpargneEstEnCreditDe100 {
    protected Compte leCompteEpargne;
    public void Given() { leCompteEpargne = new Compte() { Solde = 100 }; }
  }
}

namespace SiLeCompteEpargneEstEnCreditDe100.EtSiLeCompteCourantEstEnCreditDe10 {
  public class C_EtSiLeCompteCourantEstEnCreditDe10 : C_SiLeCompteEpargneEstEnCreditDe100 {
    protected Compte leCompteCourant;
    public void Given() {
      base.Given();
      leCompteCourant = new Compte() { Solde = 10 };
    }
  }
}


Pour que cette description de contexte compile, il me faut déclarer la classe Compte avec une propriété Solde : je viens de découvrir un "objet métier" de mon système.

public class Compte {
  public int Solde { get; set; }
}


A partir de là, je peux écrire un test qui lance l'évènement de transfert de compte à compte :

namespace SiLeCompteEpargneEstEnCreditDe100.EtSiLeCompteCourantEstEnCreditDe10 {
  [TestFixture]
  public class QuandJeTransfere20VersLeCompteCourant : C_EtSiLeCompteCourantEstEnCreditDe10 {
    [SetUp]
    public void When() {
      base.Given();
      leCompteEpargne.TransfererVers(leCompteCourant,20);
    }
  }
}


Si je veux compiler ce test, il me faut enrichir ma classe Compte : je viens de découvrir le besoin d'avoir une méthode de transfert.

public class Compte {
  public int Solde { get; set; }
  public void TransfererVers( Compte unAutreCompte, int montant ) {}
}


Maintenant, je peux vérifier mes conditions :

namespace SiLeCompteEpargneEstEnCreditDe100.EtSiLeCompteCourantEstEnCreditDe10 {
  [TestFixture]
  public class QuandJeTransfere20VersLeCompteCourant : C_EtSiLeCompteCourantEstEnCreditDe10 {
    [SetUp]
    public void When() {
      base.Given();
      leCompteEpargne.TransfererVers(leCompteCourant,20);
    }
    [Test]
    public void LeSoldeDuCompteEpargneEst80() {
      Assert.That(leCompteEpargne.Solde, Is.EqualTo(80) );
    }
    [Test]
    public void LeSoldeDuCompteCourantEst30() {
      Assert.That(leCompteCourant.Solde, Is.EqualTo(30) );
    }
  }
}


En lançant, l'exécution du test, je découvre rapidement qu'elles ne sont pas satisfaites mais, au moins, je sais pourquoi : mes résultats de test ne souffrent d'aucun manque de lisibilité !

BDDredbar.png

Pour faire passer mes tests, il me faut implémenter la méthode de transfert :

public class Compte {
  public int Solde { get; set; }
  private void AjouterAuSolde( int montant ) {
    Solde = Solde + montant;
  }
  public void TransfererVers( Compte unAutreCompte, int montant ) {
    if( Solde > 0 ) {
      unAutreCompte.AjouterAuSolde(montant);
      AjouterAuSolde(-montant);
    }
  }
}

Et voilà !

BDDgreenbar.png

A noter que si j'avais été très rigoureux, j'aurais rajouté mes conditions à vérifier une par une et j'aurais écrit le code correspondant au fur et à mesure.

A l'usage, on constate rapidement que l'écriture de tels tests avec un outil de TDD "classique" est fastidieuse. Plusieurs toolkits ont donc vu le jour pour s'adapter à ce genre d'approche "contexte/évènement/vérification". La page anglaise du BDD sur Wikipédia en propose une liste.

Voilà donc, en quelques mots, comment le BDD apporte :

  • un canevas d'écriture de test proche du langage humain : la spécification exécutable n'est pas très loin tout en gardant un outil proche du développeur !
  • une démarche de conception logicielle pilotée par les comportements attendus du système.

A mon avis, le BDD peut apporter bien plus que ça, notamment en termes de documentation du système développé et de communication entre les intervenants.
A suivre dans un prochain billet... Stay tuned !