Pourquoi les tests End to End sont réellement un enfer (et ne sont qu'illusion)

guillaume_agile

craft Softwr & Music

Posted on February 5, 2021

Pourquoi les tests End to End sont réellement un enfer (et ne sont qu'illusion)

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:

Beware of tests

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).

Warning: impersonation

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 ^^

it works on...

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.
Italian recipes

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/


  1. 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! 

  2. J'ai cité TDD cinq fois à ce stade, laissez moi vous recommander un peu de lecture sur le sujet

  3. 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

💖 💪 🙅 🚩
guillaume_agile
craft Softwr & Music

Posted on February 5, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related