Lettuce (and cucumber) is bullshit


La bonne nouvelle, c’est qu’il y a de plus en plus de mecs qui se mettent au BDD (le développement orienté par le comportement), qui est une forme de TDD (le développement orienté par les tests) mais dans lequel on test les fonctionnalités en priorité.

La mauvaise nouvelle, c’est que comme tout mouvement, il vient avec son lot de trucs hype super cool, et notamment cucumber de chez les rubystes. Bah oui, c’est fashion, ça vient forcément de Ruby. Évidement, comme les pythonistes peuvent pas supporter que les codeurs Ruby aient un truc qu’ils aient pas, même si c’est un truc parfaitement inutile, ils se sont dit qu’ils allaient pondre un équivalent : lettuce.

Les deux outils sont aussi inutiles l’un que l’autre, même si ont fait abstraction de leurs noms douteux, car après tout, c’est une marque de fabrique dans notre métier.

Le principe

Vous écrivez un scénario de tests:

Feature: Manipulate strings
  In order to have some fun
  As a programming beginner
  I want to manipulate strings

  Scenario: Uppercased strings
    Given I have the string "lettuce leaves"
    When I put it in upper case
    Then I see the string is "LETTUCE LEAVES

Vous écrivez des étapes:

>>> from lettuce import *
>>> @step('I have the string "(.*)"')
... def have_the_string(step, string):
...     world.string = string
...
>>> @step('I put it in upper case')
... def put_it_in_upper(step):
...     world.string = world.string.upper()
...
>>> @step('I see the string is "(.*)"')
... def see_the_string_is(step, expected):
...     assert world.string == expected, \
...         "Got %s" % world.string

Et pour chaque scénar, lettuce va rejouer chaque étape et voir si ça se passe bien. Avantages annoncés par rapport à la manière des pauvres mortels de coder les tests:

  • c’est élégant;
  • un non technicien peut comprendre les tests.

La faille

En plus d’être un excellent film dans lequel brille Anthony Hopkins, la faille est le détail qui caractérise la plupart de ces merveilles technologiques, à savoir: on rajoute une surcouche sur un truc que les gens ont déjà du mal à faire.

Concentrez-vous: rassemblez dans votre tête la liste de tous les gens que vous connaissez qui ont jamais écris du code.

Éliminez la majorité qui ne sait même pas ce qu’est un test unittaire.

Dans cette liste, combien écrivent des tests unittaires par eux même ? (je ne le fais pas systématiquement moi-même)

Dans cet échantillon réduit, combien écrivent des tests unittaires AVANT d’écrire le code testé (je le fais rarement moi-même) ?

Dans ce microrésidu d’individus éparse mais dont la volubilité bucolique nous incite à nous poser la question “Pourquoi Dieu a-t-il créé la limule”… Pardon je m’égare.

Donc dans ce qu’il reste, combien il y en a qui écrivent les tests en les orientant comportement en priorité, volontairement ?

Bien.

So… On va écrire tout un outil pour rajouter en travail et en complexité pour faire des tests que déjà il est terriblement difficile de motiver le dev lambda à faire (je n’ai pas dis que c’était une bonne chose, je constate, c’tou).

Parce que “c’est élégant” ? Ahem.

Parce que “un non technicien peut comprendre les tests” ?

Laissez-moi démonter ces assertions avec plaisir.

Les tests, dans la vraie vie vivante (ou VVV)

Évidement, si vous allez sur les sites des deux outils cités. Dans le premier, trouver un tuto clair prend 20 minutes (normal, c’est une communauté Ruby, c’est souvent comme ça). Dans la version Python, leur tuto fait un test sur la fonction factoriel.

Excusez-moi messieurs mais:

1 – si c’est un code aussi simple qu’une fonction factorielle, une doctest règle le problème facilement, sans avoir à apprendre une techno en plus, tout en contribuant à la documentation.

Entre leur exemple qui implique deux fichiers, dont l’un contient ça:

Feature: Compute factorial
  In order to play with Lettuce
  As beginners
  We'll implement factorial

  Scenario: Factorial of 0
    Given I have the number 0
    When I compute its factorial
    Then I see the number 1

  Scenario: Factorial of 1
    Given I have the number 1
    When I compute its factorial
    Then I see the number 1

  Scenario: Factorial of 2
    Given I have the number 2
    When I compute its factorial
    Then I see the number 2

  Scenario: Factorial of 3
    Given I have the number 3
    When I compute its factorial
    Then I see the number 6

  Scenario: Factorial of 4
    Given I have the number 4
    When I compute its factorial
    Then I see the number 24

Et une version de tests en doctests propre, pythonique, concise et qui ajoute de la doc (faudra d’ailleurs faire un article…):

def factorial(number):
    """
        Calculate the factorial of a number.
 
        E.g:
 
        >>> factorial(0)
        1
        >>> factorial(1)
        1
        >>> factorial(2)
        2
        >>> factorial(3)
        6
        >>> factorial(4)
        24
    """
    number = int(number)
    if (number == 0) or (number == 1):
        return 1
    else:
        return number*factorial(number-1)

J’ai comme un doute sur l’efficacité de leur truc.

J’en profite au passage pour signaler qu’une factorielle en Python, ce serait plutôt ça:

def factorial(number):
    number = int(number)
    if number in (0, 1):
        return 1
    return number * factorial(number - 1)

Mais là je mérite vraiment le tampon drosophilia fucker.

2 – moi dans la vraie vie vivante, mes tests ils ressemblent plutôt à ça:

    def test_get_tasks_chain_status(self):
        states = ('PENDING', 'PROGRESS', 'SUCCESS')
        try:
            celery.conf.CELERY_ALWAYS_EAGER = False
            res = chain(full_chain.s({'subject_url': 'http://www.service.com/?id=tPEE9ZwTmy0',
                                          'subject_id': 'test',
                                          'service': "service_name"}),
                   task_1.s(), task_2.s()).apply_async()
 
            tasks_id_list = get_task_id_list(res)
 
            tasks = set()
            while res.state != 'SUCCESS':
                task_status = get_tasks_chain_status(tasks_id_list)
                tasks.add(task_status['name'])
                self.assertTrue(task_status['state'] in states)
 
            task_status = get_tasks_chain_status(tasks_id_list)
            tasks.add(task_status['name'])
            self.assertTrue(task_status['state'] in states)
 
            self.assertEqual(tasks,
                             set(["tasks.full_chain",
                                  "tasks.task_1",
                                  "tasks.task_2"]))
 
        finally:
            celery.conf.CELERY_ALWAYS_EAGER = True
            # Since we are using a separate celery process for this one with
            # a different set of settings, we have to remove the generated file
            # manually
            try:
                shutil.rmtree(os.path.join(self.OLD_CONTENT_FILE_ROOT, 'test'))
            except:
                pass

On est loin de la factorielle: c’est un test de récupération de statu d’une chaîne de tâches asynchrones. Désolé les gars mais on arrête de coder des factorielles à la sortie de la fac.

Allez me décrire ça dans le DSL de lettuce/cucumber. Oh, c’est possible, ça va juste me prendre du temps. Beaucoup de temps. Parce que ce test, j’ai mis une demi-heure à l’écrire tel qu’il est: c’est la résultante de moult essais, sans compter les modifs a posteriori, car le code qu’il teste n’est pas resté en jachère.

Donc j’ai appris à me servir des tests unitaire, incluant les doctests, j’ai rajouté unittests2 et nose à ma boîte à outil, je connais le tests runner de Django, et par dessus, on voudrait rajouter une surcouche, qui me rajoute du travail dans l’écriture de mes tests. Tests déjà bien chiant à faire, j’ai vraiment PAS besoin d’un démotivateur supplémentaire.

Mais z’attendez, si encore le coût se faisait uniquement à l’écriture du test…

Ces libs, c’est une doc à lire, puis du temps sur leurs fora, et une transposition du concept pour des vrais tests et pas des fonctions playmobiles. Plus le débuggage, plus la dépendance, plus le fait que toute personne qui passe derrière moi devra perdre le même temps.

Tout ça parce que “c’est élégant” ?

Nan, le code est un langage naturel pour moi, le langage ordinaire pour écrire du code n’est pas naturel pour moi. Je suis un dev, je lis du Python au pti dej, c’est Python qui est naturel pour moi, pas un DSL boiteux qui me limite dans ce que je peux écrire histoire de se la toucher en ressemblant à un haïku. Ce n’est pas élégant.

Alors tout ça parceque “un non technicien peut comprendre les tests” ?

Vous croyez qu’une quelconque explication va permettre à une personne lambda de comprendre l’essence du test test_get_tasks_chain_status ? Je ne parle même pas de l’écrire, hein (ce que suggère la doc des outils).

Parce que si c’est juste pour que le stagiaire regarde un tableau et soit capable de citer le nom du test qui a chié, un serveur Jenkins fait très bien l’affaire, merci.

Donc, chers développeurs qui bossez sur ces outils, vous êtes brillant, compétant et vous avez la foi de faire ce faire un dev si énorme. Par pitié, transposez ce potentiel sur des trucs utiles pour la majorité des gens comme une version asynchrone de Django qui puisse concurrencer NodeJs et qui soit plus facile que Twisted, une interface Git humainement acceptable, l’édition de code collaborative qui marche out of the box pour Sublime Text, un blog qui mélange shaarli + status net + wiki + wordpress en une seule interface, un lecteur de flux RSS destop qui ait un historique des articles lus et une recherche qui marche, un jeu video de course de bagnoles qui fasse s’affronter la batmobile, la doloréanne et Bumbo, un film sur le voyage dans le temps qui ait un scénario cohérent, un téléphone non smartphone avec un lecteur mp3 décent et un clavier AZERTY, la paix dans le monde et la disparition des black mambas qui décidément ne servent vraiment à rien.

Merci

22 thoughts on “Lettuce (and cucumber) is bullshit

  • g4l4drim

    L’intéret de ce genre de techno n’est clairement pas du coté des developpeurs, mais des MOA pour l’expression de leurs exigences avec 10000 règles de gestions qui souvent se contredisent sauf sous telle condition dans l’alinéa B.

    En gros, tu leur file 15 mots clefs et ils se débrouillent pour exprimer leurs exigences avec ça. Mais c’est surtout pas fait pour des dev effectivement. En gros ça marche que dans les banques & cie ou les règles sont pas complexes mais très nombreuses.

    c’est un peu comme les CMS genre Typo pour les développeurs web…

  • residante

    Exactement !! ça me fait toujours rire les trucs qui “sont compréhensible par les non-techniciens”, alors qu’un non-technicien en aura toujours rien à foutre.

    Non franchement ces outils sont au contraire destinés à des gens qui s’y connaissent déjà et qui assimile pas mal de notions (en prog, en tests etc..). Sauf qu’ils (les outils) ne sont pas du tout efficace en situation réel.

    J’adhère vraiment à 100% avec cette phrase : “le code est un langage naturel pour moi[…]c’est Python qui est naturel pour moi,”

    Pour ma part j’utilise le couple tox+pytest. Pytest est vraiment simple et génial, et s’adapte à la taille/complexité des projets/tests.

  • Sam Post author

    @g4l4drim: certes. En même temps si les règles sont simple, je leur file un tableau, ils me liste les trucs et je les implémente. Ca va me prendre une heure up front, mais aux moins j’évite qu’ils mettent des trucs incohérents dans les tests, ce qui arrivent même aux meilleurs.

    Ok, dans le cas où il y a 1000 règles pas complexe, ça a du sens, c’est vrai.

    Mais je le sens venir gros comme une maison: le temps passé à expliquer le système et à corriger les coquilles pour les non techs vaudra largement le temps qu’on passerait à le faire avec eux. En plus on gagne en connaissance de logique métier si on travaille ensemble.

    @residante: j’ai pas essayé pytest, mais ça m’interresse franchement. Si tu te sens de faire un article dessus: je le publie avec plaisir (http://sametmax.com/appel-a-contributeurs-impertinents/) !

  • Robin Dupret

    Je suis assez d’accord avec ce qui est dit sur l’article.

    C’est vrai que pour le résultat obtenu, les BDD genre Cucumber sont une perte de temps pour le développeur. Pour débuter dans les tests ça peut éventuellement être un choix pas trop mauvais parce que c’est vraiment des phrases en anglais compréhensibles (et encore, parfois farfelues je trouve) mais vu qu’au final il faut définir certaines règles genre avec Ruby ou Python, pas sûr que ça soit efficace.

    Je n’ai jamais testé unittest en Python mais avec Ruby, je préfère utiliser un outil à mi-chemin entre minitest et Cucumber : Rspec. Il est compréhensible mais pas aussi lourd que Cucumber.

  • maarek_j

    Je ne connais pas lettuce ni cucumber, mais la meme chose existe en php “behat“.
    Je trouve le concept adapté seulement pour des tests fonctionnels et non des tests unitaires comme le cas de factoriel.
    ce qui donnerait un test qui ressemble à:

    @javascript
    Scenario: Searching for a page with autocompletion
    Given I am on "/wiki/Main_Page"
    When I fill in "search" with "Behavior Driv"
    And I wait for the suggestion box to appear
    Then I should see "Behavior-driven development"

  • Sam Post author

    D’ailleurs le BDD, c’est d’abord et avant tout du test fonctionel.

  • Romain

    (je vais tenter de défendre un peu le truc quand même)

    Le but de l’outil est de proposer un système de spécification par l’exemple qui serait exécutable. Comme ça le dev se paluche les fixtures et hop c’est bon !

    Evidemment, ça demande beaucoup de travail aux MOA qui devront spécifier leur demande de façon complète et objective. Et de laisser tomber Word pour des .features en texte plat (dur !).

    C’est bien beau mais je suis d’accord que dans la vraie vie, on n’est pas prêt de voir le graal “Spec = Test auto”.

    Encore une fois, la faille, c’est l’être humain ;).

  • residante

    @Sam j’essayerai de gribouiller quelques chose à l’occasion ;-). Après pas sur que je maîtrise toutes les subtilités, je ferai au moins un article d’introduction la dessus.

  • Nicolas Blanco

    Bonjour,

    j’apporte ma pierre à l’édifice en tant que développeur Ruby.
    Lorsque Cucumber est arrivé, il a eu effectivement pas mal de hype, mais aujourd’hui ce hype est pas mal retombé par retour d’expérience négatif.
    Par contre Cucumber a permis à beaucoup de développeurs Rails de débuter dans le test d’intégration. Quand je parle de tests d’intégration, je parle de tests qui lancent un navigateur en tâche de fond comme Selenium, à l’inverse des tests dits unitaires qui testent directement des méthodes de classes.

    Et Cucumber a permis l’éclosion de tonnes de librairies de tests d’intégration.

    D’un côté c’est bien. Le problème de Cucumber c’est sa philosophie de penser qu’une ressource non-technique va maintenir des fichiers texte de specs. Puis qu’un développeur va écrire les regexs pour parser ces règles avant d’écrire les tests d’intégration. Ça fait une sacré couche supplémentaire rien que pour des tests d’intégration.

    C’est simple pour se rendre compte de l’inefficacité de la méthode dans la vie réelle c’est de constater si la méthode continue d’être utilisée après quelques semaines/mois. Dans 99% de ce que j’ai vu : au final, ce sont toujours les développeurs qui se coltinent l’écriture et la maintenance des tests, qu’ils soient unitaires ou d’intégration. Donc les deux couches : on écrit des fichiers textes en langage humain et on écrit la couche pour les parser ne sert à rien, autant juste directement écrire les tests d’intégration.

    Au final, sur la plupart des projets je commence déjà à écrire des tests unitaires : c’est le plus important. Puis je rajoute quelques tests d’intégration pour tester les fonctionnalités majeures du site. Par exemple : la homepage charge, j’arrive à me logguer, etc.

  • Sam Post author

    Merci pour le retour vu par un rubyste. J’espérais bien que ce n’étais pas une crise de pyboutoneux.

  • Thibaut Assus

    French haters happily are not writing in english, which is a good thing.

    Have you ever used Scenario outline in cucumber for the purpose of what you want to do (testing a lot of examples) ?

  • Sam Post author

    :-p

    Yeah. And it’s still:

    – two files instead of one;
    – having to manually adjust indentation of the columns of the values you insert in the scenarios.
    – not matching my typical test complexity.

    It is better, but definitly not worth it.

  • Jean-Hadrien Chabran

    Hello, Rubyiste ici aussi.

    Il y a beaucoup de bullshit et de hype autour de cucumber, clairement. Que le client écrive les scenarios c’est de la connerie en effet. Surtout que de toute façon, rédiger les scenarios n’est pas anodin en terme de rédaction, surtout pour se focaliser sur l’aspect business.

    Par contre,
    L’intérêt est essentiellement de présenter le fonctionnement de l’application sous un aspect compréhensible par le client. Si je lui montre mes scénarios, il peut dire, oui c’est bien comme cela que cela doit fonctionner. Et ça c’est intéressant. C’est plus rapide pour lui de jeter un oeil aux scénarios que je lui présente que d’aller tester dans l’application à chaque features. Bien entendu, il testera à chaque milestone, mais cela lui donne quand même un document facile à comprendre à accepter, qui de plus est, basé directement sur le fonctionnel de l’application.

    Oui, c’est un outil qui prends un petit moment à appréhender. Le DSL “foireux”, ça me parait un agressif, à part When, Then, Given il n’y a rien d’autres que ce que toi tu définis.
    Peux tu préciser le fond de te pensée, que je comprenne mieux ce que tu y reproches ?

    Pour ce qui est de l’exemple sur les factorielles, que veux-tu, c’est un exemple. L’auteur ne va pas écrire un projet entier pour aller démontrer comment cela fonctionne …

    Après, je pense qu’il faut prendre le truc autrement vis à vis des tests. Certes, ceux qui veulent imposer le TDD ou BDD à tous sont aussi dogmatiques que les fanatiques scrums qui t’expliquent que ça marchait pas avant ™ et que ça ira mieux maintenant. Mais après, il y a des gens qui écrivent les tests en premiers, et même qui font du cucumber. J’en connais un paquet dans mon entourage. Parfois, on écrit le code avant quand le test serait trop chiant. Bref, ya pas de religion là dedans, quand tu te concentres sur le seul aspect intéressant, “produire”.

    Mais le fait que personne ne s’en serve n’est pas un argument valide pour dire que quelque chose est inutile.

  • Sam Post author

    DSL “foireux” est clairement ici une petite pique complètement arbitraire destinée à satisfaire ma propre thérapie psy à travers ce blog.

    Merci de ces retours, ils sont forts pertinents.

  • desfrenes

    Parce qu’il fallait que quelqu’un la fasse:

    “Bumbo Bumbo, petite automobile, bile, tu parrais si agile, gile, sous ton drôle de capot, bum bum bumbo…”

  • ZarioTaba

    “une interface Git humainement acceptable”

    Utilise mercurial, c’est comme git mais en simple et en python

  • Sam Post author

    Le jour où il y aura une communauté de la valeur de celle de github, sur une platforme aussi pratique que github pour mercurial, pourquoi pas.

  • H3bus

    Lettuce, cucumber, celery, je vais m’faire une p’tite salade moi tiens…

    Je suis parti par là ——————–>

  • christophe

    Bonjour, quelques remarques sur ton post.

    Pour provoquer (mais c’est toi qui a commencé :-))je te dirais que ce qui est du bullshit ce sont les tests unitaires quand on peut faire des tests d’acceptation en bdd. 80% des tests unitaires ne servent a rien si ce n’est a tester que le développeur se comprend lui même. Les vrais tests utiles sont ceux qui permettent de vérifier la cohérence entre ce que veut le client et ce qu’écrit le codeur d’ou le BDD et Cucumber.

    On ne fait pas du bdd parce que c’est élégant mais parce que c’est un langage de communication commun entre le client métier et la technique. Cela te semble moins important que d’améliorer un gestionnaire de version mais pour avoir un peu d’expérience dans ce métier je sais que le manque de compréhension entre donneur d’ordre et développeurs est le facteur numéro 1 d’échec ou de retard des projets et donc de procès.

    L’objectif n’est pas qu’un non technicien puisse comprendre les tests mais qu’un expert du métier puisse formaliser en collaboration avec un technique son savoir souvent implicite pour le rendre compréhensible avec des critères d’acceptation mesurables donc objectifs.

    Après sur le coté de l’implémentation de cucumber si tu penses que devoir écrire 2 fichiers différents est un problème je ne vois pas vraiment pourquoi parce que ce sont deux choses différentes d’un coté les spécifications en gherkin et de l’autre l’écriture du test. A moins que tu travailles sans spécifications et n’écrive que des tests l’avantage en terme de temps de cucumber c’est que spécifications et tests soient synchronisés parce que les spécifications en UML dans un beau document word qui est dépassé a la minute même ou le développeur commence a coder me semble être bien plus problèmatique et un gaspillage de temps bien plus important.

  • Sam Post author

    Si tous les gens provoquaient avec autant de tact, se serait la paix sur les forums.

  • Gilles Lenfant

    Sauf à pouvoir “obliger” le client à rédiger ses tests de recette en DSL Lettuce, son utilisation est également pour moi une pure perte de temps sachant qu’on est bien plus efficace et productif avec les classiques unittest et doctest.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.