C'est un peu mon dada depuis des lustres cette affaire; et la source de discussions très animées avec mes pairs. La pomme de la discorde? A vous de me dire...
Je ne peux cesser de l'affirmer pour l'avoir vécu de l'intérieur:
les tests End 2 End, E2E pour les intimes, sont un véritable enfer.
Un enfer pavé de bonnes intentions.
Une facilité qui se transforme très vite en handicap.
Une solution vers laquelle on plonge d'autant plus facilement quand on fait du quick & dirty, croyant que les tests bout en bout sont assez pour s'assurer du bon fonctionnement de ce qu'on livre.
Une solution trompeuse, pour ceux qui ne veulent pas faire du test unitaire et du TDD.
Un mirage pour ceux qui pensent que développeurs et testeurs ne font pas le même métier et doivent être dans des équipes distinctes (quoi que, quel est le vrai métier du tester agile? c'est une question qui fait débat également).
C'est pareil que le sucre dans l'alimentation: bon, pas cher, se trouve partout, et procure du plaisir.
Mais ca rend obèse et diabétique!
Et quand vous y avez goûté, le piège se referme sur vous.
Pourquoi?
D'abord, pourquoi en écrire, de ces fameux tests de bout en bout?
Plusieurs arguments en leur faveur:
Parce que je pense tout vérifier pour pas cher
Un test qui vérifie en une seule passe ma base de données, mon code métier, mon IHM, les services tiers, mon broker de message, mon infrastructure... Plus on est de fous, plus on rit. Mais viendra ensuite le temps de la grimace!
Parce que je ne sais pas faire autrement
Ah oui, quand on a enfin la maîtrise de Selenium, qu'on y a passé tellement de temps: on se dit qu'on a plus le temps de faire du TDD. Donc on arrête d'investir et d'apprendre.
Parce que tout le monde fait ça
C'est un peu comme dire que, puisqu'il y a autant de gens qui croient que la terre est plate, alors c'est que forcément elle l'est.
Ca fonctionne aussi pour faire croire que les résultats d'une élection ont été volés. Bref 🤨
Ca ressemble étrangement à un biais de confirmation, voir à un effet de foule.
Allons un peu plus loin, pour voir...
Que dit la littérature sur le sujet?
Il y a plusieurs pistes, dont une fait référence absolue:
La fameuse pyramide des tests.
Tout le monde la connait, mais en pratique très peu la respectent.
Pourquoi? Je ne sais pas et j'avoue que les bras m'en tombent encore et toujours...
On sait, mais on ne veut pas voir. Probablement.
Parce qu'aussi, il y a un manque de courage avéré.
Pourtant, il suffit de regarder: la pyramide est inversée. La base représente la partie la plus large. Là où tout repose. Donc elle se doit d'être solide. Et c'est là que les tests unitaires se logent.
Et par tests unitaires, je parle bien de ceux écrits dans une démarche TDD. Red, green, refactor. Pas moins. Et qui se jouent en isolation.
Le haut de la pyramide, c'est un peu la cerise sur le gâteau. Là où beaucoup trop de gens y voient écrit "End 2 End", il aurait mieux fallu marquer: Exploratory ou Smoke tests.
J'entends encore (et toujours) dire que les tests unitaires c'est compliqué à faire, et que le TDD, bah, ca prend du temps (ce qui est totalement faux). Et surtout ça demande des compétences, donc "on verra plus tard".
Et le "plus tard", c'est justement le test E2E. Celui qui vient à la fin. Et qui fini par prendre une place prépondérante dans une intégration continue. A tort.
Si Uncle Bob, Martin Fowler, Kent Beck ne manquent pas d'avoir proféré toutes les injonctions à l'endroit du TDD et à l'encontre du E2E; d'autres auteurs n'ont pas toujours été aussi clair.
Dans un livre pourtant bien écrit (Unit Testing Principles, Practices, and Patterns ), Vladimir Khorikov est revenu tout récemment sur la mésinterprétation croissante de son propos sur les tests E2E; il en a fait plusieurs billets dans sa newsletter et je reprends quelques un de ses arguments ici.
Et puis il y a les ultra convaincus, qui n'y vont pas par quatre chemins: les tests E2E sont une arnaque! (pour rester polis).
Combien ca coûte en vrai?
Très cher! Trop cher!
Un test E2E parait facile quand on regarde les 2 tutos et les 3 bouts d'exemples qui trainent partout.
Un test E2E ça va, 10 tests bonjour les dégâts.
Ils prennent du temps pour s'exécuter. La boucle de feedback est beaucoup trop longue!
J'ai travaillé avec des éditeurs logiciels chez qui il fallait lancer les testes E2E (tous piloté par Selenium svp) le vendredi après midi et prier le weekend pour espérer voir un résultat viable le lundi matin.
Car ces tests pouvaient échouer lamentablement, non pas à cause d'un vrai bug dans le code, mais parce que toute la machinerie mise en branle était fragile et cassait souvent toute seule.
Un naufrage.
D'ailleurs ce sont des sociétés (que j'ai connues) qui ont plié boutique depuis.
Donc je vous parle en termes purement économiques. Financiers. Money!!!
Sont-ils fiables ?
NON!
Les tests E2E ont comme principales caractéristiques de vouloir tout tester, donc de tirer toutes les dépendances du monde; cela peut être (liste non exhaustive):
- des APIs tierces
- des bases de données / système de fichiers
- des ressources externes
- des systèmes de communication (vulgairement des Entrées/Sorties)
- des librairies tierces
- le temps! (indéterminisme)
- la génération de nombres aléatoires, et tout ce qui se base dessus: numéros de séquence unique (Guid), Cryptographie...
Et pour ajouter de la difficulté dont on se passerait bien, certaines de ces dépendances peuvent présenter des comportements asynchrones et/ou indéterministes.
Beaucoup se risquent à régler les 2 derniers problèmes avec des exceptions, ce qui ne règle rien, alourdit votre code (en temps d'exécution et pire en lisibilité) et rend l'ensemble encore plus sujets aux bugs. Bref, rien ne va!
Deux symptômes apparaissent:
Les faux négatifs
Quand un test E2E plante c'est souvent parce qu'une de ses dépendances a planté.
La base de données ne répond plus ou n'a pas le bon jeu de données initiales par exemple. Ou -horreur suprême- les tests doivent être joués dans un ordre donné, cad on a un test qui dépendrait d'un autre (là, on frise le mental breakdown).
Hors ce n'est pas le comportement du système tiers que vous voulez tester, car celui-ci est déjà testé par ailleurs (ou devrait l'être par son fournisseur si celui était un minimum sérieux).
Ce que vous voulez savoir c'est comment vos composants, un ^par un - en particulier celui qui fait l'interface (ou mieux l'adaptation) avec le dit système externe, se comporte en cas de problème. Point.1
Un faux négatif reflète un test coûteux à maintenir, car il vous faudra pas mal de temps (surtout pour chercher d'où vient le problème puisque vous faites jouer toutes les dépendances en même temps avec des effets de bord de partout) pour le debugger et lui redonner un comportement normal. Ou bien il aura tendance à revenir instable assez souvent.
Les faux positifs
Ce sont les tests qui restent verts alors que vous venez de changer le comportement d'un bout de code quelque part. Difficile de les identifier dans les End 2 End.
Toutes les dépendances traversées vous amènent à écrire des tests qui ne testent pas vraiment ou pas entièrement votre code, in fine.
Il arrive que supprimer des lignes de notre code n'impacte aucun test E2E, car cela tombe hors des radars des dépendances.
Du coup, vous livrez un bug mais vous ne voyez rien passer.
Par contre, vos utilisateurs ne manqueront pas de tomber dessus très rapidement.
Alors, c'est quoi un bon test?
Il doit avoir 4 grandes propriétés:
Protéger contre les régressions
Une modification de code entrainant un retour en arrière sur un comportement attendu devrait faire passer au moins un test au rouge.
Pour éviter cela, vous pouvez vous doter d'un outil pour faire du test de mutation (mutation testing).
Donc absence de faux positifs (des tests qui resteraient verts alors qu'ils devraient être rouges).
Donc détection de bugs efficace.Résistant au refactoring
Le refactoring de code (changer du code sans changer son comportement) ne doit pas faire passer vos tests existants au rouge.
Donc absence de fausses erreurs (faux test rouge).Donner un feedback très rapide
Un test ne devrait pas prendre plus de quelques dixième de secondes pour donner son résultat.
Donc pas d'attente, pas de boucles, pas de sleep ou wait (sérieusement? vous pensiez tester comme ça du code asynchrone??? 🤯). Personne ne devrait accepter attendre plus d'une demi-heure pour avoir les résultats d'une campagne de tests.Maintenable
Des journées à écrire un test, à le faire passer au vert puis à le modifier ou le consolider?
Allons, allons. Il est temps de faire table rase de tout cela.Il faut qu'il soit facile à écrire, et facile à lire bien entendu.
Plus il y a de lignes de code dans un test, moins solide il sera.
Et attention au code caché (par exemple derrière de la configuration et donc des dépendances).Ne pas mocker ce qui ne nous appartient pas (je reviendrai là dessus).
Plus le test est dur à faire tourner: reboot un serveur, restaurer une base de données, résoudre des problèmes de config ou de réseau... plus ce sont des dépendances dont il vous faut vous défaire. Par plus d'abstractions.
“Push tests as low as they can go for the highest return in investment and quickest feedback” Janet Gregory and Lisa Crispin
Payez vous une cure de désintox des tests E2E
Les tests End To End sont une facilité, pas une fatalité.
Avec de la pratique et de la rigueur, on peut les limiter à un très très petit nombre et les remplacer avec une meilleure efficacité (couverture technique ET fonctionnelle améliorée) à condition de bien s'y prendre.
Il vous faut procéder par petit pas:
1er pas: débranchez tout! (vers des tests hermétiques)
On teste en isolation. Il y a des milliers de lectures à ce sujet, je m'y étendrais pas cette fois ci;
Voici ce qu'en pensait un ingénieur de chez Google en 2014 .
On peut dire qu'un bon test devrait pouvoir tourner sur n'importe quelle machine sans connexion réseau.
Oui, oui. Vous avez bien lu.
Surtout, surtout, faites tout pour éviter le coup: "mais ca marche sur ma machine, je ne comprends pas!".
Et ne comptez pas sur le DevOps pour vous sauver, au contraire ^^
2e pas: une conception objet "test oriented"
Si vous faites du TDD2, et suivez les heuristiques SOLID intelligemment, alors vous devriez automatiquement vous sentir légitimes pour dire: "les tests E2E sont une complète aberration, car ils mettent à mal toutes les règles que je tente d'appliquer":
- Single Responsibility
- Open for extension / Closed for modification
- Liskov Substitution
- Interface Segregration
- Dependency Injection
Appliquez ces heuristiques à tous vos tests, et effectivement vous verrez, les tests E2E sont à coté de la plaque!
Le code de VOS tests est aussi VOTRE code. Il obéit aux même lois.
3e pas: vers une architecture "test oriented"
Et pour cela, l'architecture hexagonale a fait ses preuves. J'y reviendrai sûrement dans un prochain article.
Cet architecture préconise de mettre en place des Ports et des Adapteurs, qui permettent justement de ne pas tout tester en même temps, d'éviter les tests E2E. Si on a les bons niveaux d'abstraction bien sûr.
4e pas: le métier est vérifié et codé dans des Bounded Contexts
Je reprends ici les termes du Domain Driven Design, car même sans faire d'Event Sourcing, DDD nous propose de découper, délimiter, responsabiliser le domaine métier et ses règles; et évidement de le découpler de tout ce qui est autour (UI, persistance, services externes, sous domaines, domaines génériques, domaines support).
Ce qui va découper, découpler votre système et donc rendre redondant (voir impossible) du test de bout en bout. Et tant mieux.
Mais comment peux tu être sûr ?
C'est à peu près du même niveau que de me dire: "comment peux-tu être certain que la terre est ronde, parce qu'à l'œil nu elle apparait plate".
Il y a toujours eu des gens pour me dire: "mais tu ne peux pas avoir de certitude à 100%" sans les tests de bout en bout.
Je ne dis pas pour autant qu'un test E2E est inutile. Parce que la vraie question est "que teste-t-on"?
Ou plutôt :
"Que peut-on tester?"
Si on ne dispose d'aucun outillage de tests efficace, alors oui, il ne reste (hélas) que le test E2E.
Mais vouloir tout tester avec un E2E est une perte d'argent et de temps comme je ne cesse de le répéter ici.
C'est là où tout le monde s'embrouille sur la pyramide de tests quand on lit: "integration tests".
Pour beaucoup, tester en intégration c'est tester tout ce qui peut s'intégrer, ensemble, en même temps. Et bim!
On revient sur du E2E.
Tout comme ceux qui ont confondu les tests E2E avec les tests de l'Interface Graphique.
Parce que c'est tellement facile (en apparence) à coder avec des outils comme Selenium ou Cypress.
Parce que justement leur architecture spaghetti, ou N-tiers ( = plat de lasagne), ou maintenant Micro Services ( = plat de raviolis) ne permet plus de séparer la UI (User Interface) d'un monstre monolithique où tout est collé bout à bout.
Pourtant, le but est de tester si les composants qui ont, par ailleurs, été testés de manière isolée, s'intègrent bien entre eux.
Une fois cela vérifié, pourquoi l'ensemble ne serait-il pas stable?
Integration ou Contrat ?
Une intégration fiable se joue autour d'un contrat fiable.
S'assurer que le contrat est respecté, DES DEUX COTES, c'est faire une intégration qui tient la route.
Il faut vérifier (automatiquement de préférence) l'équivalence du contrat des 2 cotés d'un composant: fournisseur (provider) ET consommateur (consumer).
Quand on fake (parlons de doubler, comme au cinéma), le test n'est valide que si la doublure est ressemblante.
Il faut s'en assurer constamment (à chaque build, à chaque intégration continue), le contrat ne doit pas être rompu 3.
La bonne idée pour éviter des vérifications superflues, c'est de laisser le compilateur les faire par le typage. Un typage très fort et un respect des règles SOLID. Et ne pas utiliser les exceptions. Et suivre toute la démarche DDD, cela aide beaucoup.
Les bénéfices de l'abstinence
Les gens qui mettent (perdent) tout leur argent dans le E2E testing, sont hélas ceux qui n'ont pas pris soin d'explorer d'autres pistes.
Et pourtant, il y a des choses bien plus profitables (rentables, messieurs les financiers) comme le Smoke Testing, le Sanity Testing et le Regression Testing.
Et s'il ne devait en rester qu'un
Et bien, un test "End to End" , de bout en bout, pourrait bien s'avérer utile pour tester ce qu'on ne sait vraiment plus tester autrement, et c'est peut être justement l'intégration de l'intégration.
L'articulation ultime.
Mais je suis sûr qu'on peut le remplacer par un autre test d'intégration, si on cherche bien ;)
Et à ce moment là, que dois je vérifier?
Que mon système démarre bien. C'est tout.
Derrière cela veut dire que ma config est juste et que mes systèmes sont opérationnels. Mais cela aussi je peux le surveiller ("monitorer") par d'autres tests plus unitaires.
Juste un test, un happy path. Un seul. Pour convaincre le dernier des incrédules.
Je ne me laisserai même pas tenter par une liste de "High Value Paths" comme on peut le lire par ici ou là; parce que si vous entrez dans cette logique, vous allez en écrire autant que de Use Cases de votre métier (c'est à dire: pléthore).
Alors que toute la logique devrait être dans le Domaine Métier et les agrégats qui le constituent tel que nous l'apprend DDD.
C'est l'objet du prochain article que je suis en train de peaufiner.
Merci de m'avoir lu jusqu'ici.
Références
https://www.stevesmith.tech/blog/end-to-end-testing-considered-harmful/
-
Analyser/prouver comment ensuite l'éventuel problème (déclenché par un système tiers) se propage à l'intérieur de votre ensemble de composants, devrait être réglé par une programmation propre (principes SOLIDES encore, mais aussi ne pas utiliser les exceptions, utiliser des monades de type Result ou MayBe ou Optional). Finalement se résoudre en un problème de typage et donc de compilation, pas de tests à proprement parler. Ca serait tellement plus rapide à vérifier! ↩
-
J'ai cité TDD cinq fois à ce stade, laissez moi vous recommander un peu de lecture sur le sujet. ↩
-
Le bon outil, me semble-t-il, c'est le Contract Based Programming/Testing. Oui c'est un travail supplémentaire. Mais je ne vois pas comment l'éviter pour ne pas retomber dans le piège du E2E qui finalement est un pis-aller pour ceux qui ne prennent pas le temps de faire soit du typage fort, soit du Contract Testing (Consumer-driven contract testing (CDC) expliqué ici ) ↩
Top comments (5)
Merci de l'article qui est intéressant au-delà de donner des références bien utiles.
Au début des années '90 mon entreprise a mis sur le marché une collection de logiciels (24 titres, > 300000 copies vendues) et nous n'avons eu qu'1 seul bug répertorié causé d'ailleurs par une "amélioration" de dernière minute.
À titre de provocation ... nous ne faisions AUCUN test, du moins formalisé tel qu'il aurait fallu le faire. Par contre, tout ce qui était développé était mis en pratique immédiatement (le plus vite possible, dirais-je) et même si nous n'étions pas dans un environnement réseau (1989-1990, DOS 3.2 et 3.3), nos applications étaient complexes car elles "dialoguaient" les unes avec les autres : "« Je Gère Mes Factures » parlait à « Je Gère Mes Clients et Fournisseurs », « Je Gère Mon Stock », « Je Gère Mes Addresses », etc.
Nous avions appelé notre méthode de testing le "Testing chaotique".
Comme nous utilisions nos propres réalisations (Eat You Own Dog Food), tout ce qui n'avait pas été éprouvé par l'équipe de développement (3 personnes) était utilisé et donc ... testé dans la vie réelle par ceux à qui la solution s'adressait. La secrétaire utilisait la gestion de factures pour faire les factures aux clients, les commerciaux utilisaient la gestion des clients et fournisseurs pour faire leur travail, etc. Au fond, nous mettions à disposition de nos propres troupes ce qui avait été développé et laissions les bugs faire surface dans un environnement que nous contrôlions : notre propre entreprise.
Cela nous a d'ailleurs amenés à considérer le fait que nous avons ensuite labellisé sous l'acronyme "AMIS" : Anomalies, Manquements, Inadéquations et Suggestions.
Ne pas contraindre qui que ce soit à un plan de test permettait la découverte : l'utilisateur usait du logiciel comme il lui semblait naturel de le faire et ce qui est naturel pour l'un ne l'est pas pour l'autre. Cela nous amenait donc à découvrir des manières de faire que les développeurs n'avaient même pas envisagées au départ. Comme l'interaction avec nos utilisateurs était libérée de toute contrainte, nous avons été à même de collecter d'autres choses que les sempiternels bugs : des manquements (ce qui devrait se trouver dans le logiciel mais qui ne l'est pas), les inadéquations (ce qui fonctionne comme cela a été analysé mais qui n'est pas approprié) et les suggestions (de nouveaux développements à réaliser, des modifications, des améliorations). Il est clair que les tests E2E ne peuvent pas couvrir ces matières !
Et nous sommes parfaitement d'accord : les tests E2E sont extrêmement coûteux et, au plus profond de l'âme de l'équipe de développement, n'apportent aucune certitude supplémentaire par rapport aux tests effectués en amont.
Ce dont tu parles ressemble bien à du Exploratory testing et du Smoke testing.
Ca fait partie de la pyramide des tests.
Oui, effectivement mais le "Exploratory Testing" c'est grosso modo en 1998 que cela démarre réellement et que le smoke testing c'est entre 1979 et 1985 (avec une courbe d'adoption particulièrement plate). Nous, quand on démarre cela, on est en 1989.
Nous avons d'ailleurs établi une série de contraintes dans ce testing. Par exemple, il faut lui donner du temps. C'est un peu comme les robots de tonte de pelouse. Après un certain temps, on est sûr qu'ils ont tondu partout mais si on ne leur octroie pas ce temps, alors la tonte semble ... chaotique.
;-)
Article très intéressant, merci.
Très bon article ; je trouve les remarques pertinentes !
Je vais le partager avec mes collègues.
J'attends avec impatience l'article sur DDD (sujet qui me passionne)