/736x/14/35/8f/14358f44099aa4d82ffb1330daad098a--youtube-search.jpg Gherkin vs. Approvals | HadrienMP

HadrienMP

Gherkin vs. Approvals

22/02/2023

TL;DR : Traduire le langage de vos expert.e.s métier en code, c’est très compliqué. Il y a mille manières de dire une chose en langage naturel. Contre une seule dans votre code.

Pourtant de la documentation lisible et toujours à jour, ça a beaucoup de valeur.

Alors prenons le problème à l’envers, générons notre documentation dans les tests. À la place de l’assertion, on compare, avec un diff, l’ancienne documentation et la nouvelle. Les différences sont des régressions.

C’est beaucoup plus simple car on peut représenter d’une seule manière états et transitions.


Pour simplifier la suite, je m’attaque à la pratique d’écrire ses tests en Gherkin, pas à la notation Gherkin qui est très bien.

Gherkin

C’est quoi ?

C’est une notation qui encadre l’écriture de tests en langage naturel. Si vous ne connaissez pas son petit nom, elle est reconnaissable par les mots clés “Given, When, Then”. C’est la notation d’outils comme cucumber, specflow, fitnesse etc. Ça peut ressembler à ça :

Given I am logged in as an admin
When I go to a blog post
Then I can delete a comment

Pour quoi faire ?

Faire écrire nos tests par les expert.e.s métier par exemple. Même si, en vrai, on voit rarement des équipes où ça fonctionne.

Mais même s’ils sont rédigés par les devs, la modélisation en code atteint vite ses limites. C’est pourquoi j’aime beaucoup utiliser le tableau blanc pour expliquer des concepts, des fonctionnalités, des architectures. Un test en Gherkin bien écrit peut être bien plus lisible que du code.

Si on prends l’exemple du jeu du morpion qu’on a développé avec Thomas Carpaye, nos tests ressemblent à ça

emptyGrid
  |> play X (0,0)
  |> play O (0,1)
  |> play X (1,0)
  |> play O (1,1)
  |> play X (2,0)
  |> decide
  |> Expect.equal (Win X)

Avec un Gherkin bien fait on pourrait avoir

Given the grid
|   |   |   |
| O | O |   |
| X | X |   |
When X plays on the bottom right
Then X wins

C’est plus lisible. Même si on aurait pu faire mieux pour le code en Elm.

De la colle et des menteurs

Il faut maintenant pouvoir produire un test depuis la notation Gherkin.

Pour chaque fragment de Gherkin il va vous falloir définir un parseur pour extraire les valeurs intéressantes. Une fois que vous avez vos valeurs, vous écrivez votre scénario de test comme d’habitude dans votre langage de programmation. En gros, il vous faut tout une couche de glue entre le domaine du langage naturel et le domaine du code.

En tant que devs on a l’habitude des couches de glue. C’est pas grave, tant qu’elles ne sont pas trop grosses.

Collons un coupon de réduction avec cucumber.js

Le scénario (qui sera sauvé dans un fichier séparé)

Given a coupon of 10$ with a minimum of 50$ of purchase
When the shopping cart is of 50$
Then the amount billed is 40$

Le fichier “steps” de cucumber.js

const defineSupportCode = require('cucumber').defineSupportCode;

defineSupportCode(({ Given, Then, When }) => {
  let coupon = null;
  let actualBilledAmount = null;
  
  // C'est ce que j'appelle un parseur
  Given('a coupon of {amount}$ with a minimum of {minimumPurchase}$ of purchase', 
        (amount, minimumPurchase) => {
          coupon = {amount, minimumPurchase};
        });
  When('the shopping cart is of {amount}$', 
      (amount) => { 
        actualBilledAmount = applyCoupon({coupon, cartTotal: amount});
      });
  Then('the amount billed is {amount}$',
      (amount) => { 
        expect(actualBilledAmount).toEqual(amount);
      });
})

Avec de la magie, cucumber va répertorier tous vos parseurs et vos fichier de scénario. Il va trouver le parseur qui correspond à votre scénario et vous sortir un rapport de tests.

Instant police

Une des techniques de police pour reconnaitre un menteur c’est de poser la même question plusieurs fois. La personne qui ment récitera son mensonge. Elle utilise les mêmes mots, les mêmes expressions etc. À l’inverse, la personne sincère modifie naturellement son discours. Elle traduit en fait à la volée un modèle mental, des souvenirs.

Donc, si vous questionnez votre expert.e métier, vous aurez des règles exprimées différemment à chaque fois. Si vous avez plusieurs expert.e.s, c’est encore mieux ! Chacun.e aura sa manière de s’exprimer !

50 nuances de coupons de réduction

Dans l’exemple précédent, on avait un exemple de scénario de coupon de réduction :

Given a coupon of 10$ with a minimum of 50$ of purchase
When the shopping cart is of 50$
Then the amount billed is 40$

On pourrait aussi l’écrire comme ça

Given a shopping cart of 50$
And a coupon for 10$ off after a 50$ purchase or more
When the coupon is applied
Then the bill is for 40$

Voire encore :

Given I purchased 50$ of groceries
When I present my coupon for 10$ off for a 50$ purchase or more
Then I will pay 40$

Et je ne m’arrête que par souci de longueur de cet article 😇.

Aïe !

Ça commence à sentir mauvais non ? Si notre ensemble d’entrée en Gherkin est gigantesque, notre ensemble de sortie en code est petit. On va avoir beaucoup de glue. Le fichier de parseur d’une fonctionnalité va grossir pas mal. Et quand vous ajoutez à ça la nécessité de réutiliser les steps entre des fonctionnalités différentes, ça devient un sacré fouilli.

CodeGherkin

Mais alors, que faire ?

Réduire l’ensemble d’entrée ?

Une des manières de le faire est de n’autoriser qu’une seule formulation “naturelle” par fragment de code.

On crée un set de vocabulaire restreint qu’on va parser pour le transformer en un test exécutable. Tiens c’est marrant, ça ressemble beaucoup à la définition d’un langage de programmation ! Oui. On vient juste de créer un nouveau langage de programmation pour notre domaine. On appelle ça un “Domain Specific Language”. Pas de problème avec les DSL en soit, c’est même plutôt cool. Mais, de facto, les personnes qui l’écrivent sont des devs, même si leur rôle est Product Owner.

Même avec cette solution il va vous falloir un certain outillage pour que ça fonctionne :

Ça fait pas mal de boulot. Rendus là, je préfère clairement écrire du code directement et faire un effort de lisibilité pour pouvoir écrire les tests en binôme avec l’expert.e métier.

Approvals

C’est quoi ?

Écrivez vos cas de tests comme d’habitude, mais plutôt que de construire une assertion, construisez une documentation lisible de ce cas. Documentez le setup, l’action et la sortie. Vous pouvez l’écrire dans un fichier Markdown, AsciiDoc, Graph même Gherkin si vous voulez !

Une fois la documentation générée on la vérifie à la main et on l’approuve (en renommant le fichier en .approved par exemple). Chaque fois qu’on relancera les tests, ils compareront la documentation générée avec celle approuvée. Chaque différence est une régression.

La librairie approvals pourra vous aider à gérer simplement vos fichiers, assertions et approbations.

Reprenons l’exemple du coupon de réduction !

describe('Minimum purchase coupon', () => {
  it('is substracted from the cart amount', () => {
    // Given
    const coupon = { minimumPurchase: 50, amount: 10 };
    const cartTotal = 50;

    // When
    const billedAmount = applyCoupon({ coupon, cartTotal });

    // Then
		// verify produit une assertion, donc vous lancez ce test avec votre test runner habituel.
    approvals.verify(`
    Given a coupon of ${coupon.amount}$ with a minimum of ${coupon.minimumPurchase}$ of purchase
    When the shopping cart is of ${cartTotal}$
    Then the amount billed is ${billedAmount}$ 
    `);
    // Ici je produit de la notation gherkin, on commence à bien la connaitre, autant l'exploiter !
  });
});

// approvals va créer un fichier dans le même dossier que le test
// minimum_purchase_coupon.is_substracted_from_the_cart_amount.approved.txt
// et sa version .received.txt

Pourquoi c’est mieux ?

La complexité est bien moindre car l’ensemble d’entrée est réduit. Vos états et transitions sont exprimées d’une seule manière dans le code. Ils peuvent potentiellement être représentés d’une seule manière dans la documentation.

CodeDoc

De plus, comme on s’affranchi de la contrainte du parsing, on peut être plus créatifs sur la forme de cette documentation. On peut construire des graphs, des SVGs animés etc.

On pourrait imaginer de l’ascii art pour notre exemple de coupon !

+----------------------+       o--\           |                 
|       10$ off        |           \    50$   |     =>  40$ to pay
| for a minimum of 50$ |            \_________|  
+----------------------+             O       O   

Et on peut toujours prendre notre expert.e métier et écrire les tests à plusieurs. Grâce au support de la documentation, tout le monde pourra comprendre.

Anecdote

Avec Thomas CARPAYE (oui encore), on utilisé cette technique pour refactorer du code legacy.

On avait trouvé un fragment de code à refactor qui était incompréhensible. On a fait une combinatoire des valeurs intéressantes pour nous servir d’entrée de tests et sorti un gros fichier markdown avec le setup et le résultat pour chaque entrée. À chaque opération de refactor, on relancait notre test et on voyait si on avait cassé quelque chose ou non.

Le gros avantage ? Quand on trouvait que ce qu’on avait en sortie semblait aussi, voire plus, logique qu’auparavant on amenait notre doc à notre expert métier. Il pouvait très facilement nous dire qui a raison, avant ou après. Très puissant.

Conclusion

J’adore la documentation visuelle. Je retrouve plus de ce qui me plaisait dans le Behaviour Driven Development avec la génération de documentation qu’avec la génération de tests à partir de Gherkin.

Comme d’habitude, je sais que mon opinion est controversée. La communauté aime beaucoup les outils Gherkin (style cucumber) et tant mieux si ça fonctionne dans vos équipes. Mais que diriez-vous de tenter la génération de doc pour voir ce que ça donne ?

Pour aller plus loin

Pour aller plus loin sur le sujet, cette technique d’approvals est bien expliquée par Sébastien FAUVEL à bdx.io.

Elle est inspirée de la philosophie living documentation de Cyrille MARTRAIRE, n’hésitez pas à aller jeter un oeil à son livre sur le sujet. De la doc qui est toujours à jour par design, qui pourrait dire non ?

Commentaires

Utilisez votre compte Fédivers (Mastodon par exemple) pour commenter ce post.