Ecritures des tests : cas concret

Connaissez-vous des articles qui traitent de l’écriture de tests (unitaires, fonctionnels, non regressions, etc) sur des cas concrets d’applications métiers ?

Car souvent c’est seulement pour tester la fonction addition …

Je lis souvent qu’il faut “savoir” écrire les tests, étant dev Asp.Net MVC j’aimerais trouver un exemple complet de bout en bout.

1 « J'aime »

Je n’ai pas d’exemple d’article mais je peux te dire comment je fais.

  • J’écris un scénario sur l’utilisation que peut faire une personne de la fonctionnalité que tu implémentes. Je fais juste le “happy case” pour rester simple car les tests d’acceptation sont lents;
  • J’exécute le scénario pour le voir échoué et me donner la prochaine étape;
  • J’écrit un test unitaire pour la prochaine étape;
  • J’exécute le test unitaire pour le regarder échoué;
  • J’écris le code nécessaire pour passer le test unitaire;
  • Je réexecute le test d’acceptation et recommence la boucle;
  • Je fait de la refactorisation quand tout est vert;

Je fais un autre scénario ou j’en modifie un existant quand il y a un bug. J’essai de minimiser le nombre de scénario écrit car c’est long à exécuter.

Ce n’est pas très détaillé mais c’est le principe de base. As-tu besoin de précisions ? Je peux répondre à tes questions si tu en as.

Je peux conseiller le livre Growing Object oriented software guided by tests. Il aborde le sujet dans le cadre du développement d’une application complète. Ça couvre les tests d’acceptation, les mocks, le tdd…

Merci pour vos réponses.

@guirec_corbel avec ce scénario tu arrives à couvrir convenablement une appli complexe web ? Sur le temps total de dév, la partie écriture des tests est de combien ?

@rg701653, il n’y a aucun problème avec une appli web. Je n’ai pas fait de statistique détaillé sur le pourcentage de temps que je met sur les tests. Je dirais 50%. Évidemment, écrire des tests c’est long, mais tu fais un produit de meilleur qualité, une meilleure conception de ton application et tu minimises le nombre de bugs.

Mon petit workflow pour les tests :

  • Ecriture du scénario, je mets tout en “skip” (ou pending dans certains frameworks) pour voir ce qu’il me reste à faire sans que ça génère des échecs sur quelque chose qui n’a pas encore été réalisé. Du coup j’ai 3 états : skip, success et fail.
  • Lorsque je m’attaque à une feature, je retire le “skip”, à partir de là c’est soit “success”, soit “fail”.
  • Je code la feature ainsi que les tests positif et cas d’échecs pour m’assurer que le programme se comporte correctement sur les échecs.

La feature et ses tests sont soient codés en parallèle, soit l’un avant l’autre selon ce qui m’arrange.

Oui voila c’était ma prochaine question.
J’avais l’impression que tout le monde ne faisait que du TDD et personne ne codait le test après.

J’ai vu qu’il existait plusieurs méthodes pour écrire ses tests (TDD, BDD, etc), quelqu’un les a-t-il tous déjà testé ?

J’ai tenté d’initier un projet de test sur mon projet Web (Asp.Net MVC) c’est assez usine à gaz pour reproduire le context web.

@rg701653, Ce que @Nicolab et moi et faisons, c’est du BDD. Le principe est simple, tu commences par faire un test représentant le comportement que tu voudrais avoir dans ton application et tu continus jusqu’à ce que celui-ci passe. Si l’on faisait du TDD, nous écrierions des tests unitaires pour chaque module mais pas un test représentant le comportement de l’utilisateur.

D’après ce que je comprend @Nicolab fait uniquement des tests d’intégration et moi j’ajoute des tests unitaires. Dans un test unitaire, un test doit être écrit pour tout les cas d’utilisation de la fonction que tu ajoutes. Le classes est isolées des autres en utilisant des stubs ou des mocks, ce qui correspond à des faux objets. Un test intégré est plus facile à faire mais est plus lent à s’exécuter étant donné que tu vas interagir avec la base de données dans la majorité des cas.

@Nicolab semble de faire que test de haut niveau. Encore une fois, c’est plus rapide à faire. Cependant, c’est très lent étant donné qu’il faut utiliser un navigateur. Ce qui, même headless, te pénalise de quelques secondes ou minutes à chaque fois. C’est pour cela que je préfère faire des tests unitaires.

Gagner quelques secondes/minutes peut paraître dérisoire mais, quand les tests doivent être exécutés souvent, à chaque fois que tu fais quelque chose. Tu veux avoir un feedback le plus rapide possible pour que ton développement soit fluide. Si tu perds 2 secondes sur un processus que tu exécutes toutes les 10 secondes c’est comme si tu mettais 20% de ta journée à la poubelle.

Si tu fais tes tests après ton code, tu perds 75% de l’intérêt de l’écriture des tests. Le BDD n’est pas uniquement une question de non-régression, c’est avant tout un outil qui va te permettre d’avoir une meilleur conception de ton application. Au début, tu ne sais pas à quoi va ressembler ton code, tu sais juste ce que tu veux. Le scénario va te dire ce que tu dois faire par la suite pour atteindre ton but. Les tests unitaires vont pouvoir cibler un comportement spécifique, ça remplace des tests que tu pourrais faire dans la console et c’est beaucoup plus rapide que de le faire manuellement.

Dans ma réponse précédente, je te disais que ça prenait 50% du temps à faire les tests mais c’est probablement la même chose pour les tests manuelles. À chaque fois tu vas devoir prendre ta souris, entrer les champs correspondants et regarder l’exécution.

J’ai encore pleins de trucs à dire sur le sujet… c’est passionnant. N’hésites pas à poser des questions.

@rg701653 je te rassure, parfois ça m’arrive d’écrire les tests après ^^

@guirec_corbel du tout :)
Je fais majoritairement des tests unitaires.

En gros je fais systématiquement les tests unitaires des libs, puis les tests unitaires de l’application en général pour assurer le comportement des couches qui la compose, puis je finalise par quelques tests d’intégrations (plus ou moins poussés suivant l’importance du projet).

Moi aussi les tests unitaires est un sujet qui me passionne, (je me suis d’ailleurs créé Unit.js pour faire les assertions dans un style que j’aime bien, avec quelques helpers pratiques, etc).
Si vous avez quelques tests unitaires en public, ce serait intéressant de voir les différentes approches et les orientations différentes suivant les langages de programmation.

En voici quelques-uns que j’ai fais :

Merci pour ces explications détaillées (je met tout ça dans un coin et viendrait le relire petit à petit).

Une des questions que je pose par exemple :
Imaginons que l’on souhaite créer un écran de création d’utilisateur. Si j’ai bien compris :

  • On créé le test
  • On créé la méthode dans le controller
  • On a besoin de vérifier que les paramètres soient valides dans la méthode
  • On créé le test de la méthode de vérification
  • On inclus la méthode de vérif
  • On a besoin d’accéder à une BD
  • On créé le test de la méthode d’accès
  • On créé la méthode d’accès que l’on insère dans la méthode de création du user

C’est cette gymnastique d’esprit ?

@rg701653, évidemment les cas peuvent variés selon l’application mais la première partir n’est pas de créer des tests pour le controller. Il faut créer des tests de plus haut niveau. En ruby, j’utilise Capybara. Celui te permet de tests l’interface utilisateur. Comme pour remplir un formulaire, cliquer sur un bouton, etc. Ensuite, il va probablement te dire que ta page est inaccessible ou qu’elle ne contient pas les bons éléments. Tu n’est pas forcement obligé de faire des tests pour la vue. C’est à toi de voir. À un moment donné, il va te dire qu’il te manque une fonction dans le controller, c’est à ce moment que tu vas devoir t’en occuper en faisant un test. Il faut que tu laisses tes tests te dire ce qui ne fonctionne pas et que tu travailles au fur et à mesure.

Je déterre un peu le sujet…

L’idée est de créer un service de création d’utilisateur. Tu spécifies ce que dois faire fonctionnellement le service (informations attendues, types de contrôles), et tu traduits ça en test avec l’outil que tu préfères utiliser. A priori, les gens qui sont plus dans le BDD vont écrire tous les tests dès le début. Les gens qui sont plus TDD vont y aller test par test (pour des raisons qu’on pourra détailler dans un autre fil).

Après, considère que tu vas faire abstraction de ton infrastructure : tu vas mocker l’accès à ta BDD en la représentant par une classe repository. De même, ton service devra être agnostique de la façon dont les données vont arriver : ça peut être une requête http, un fichier, une CLI…

Quand tous tes tests passent, tu auras validé fonctionnellement ton service. Tu peux alors le brancher sur ton infra :

  • Le rendre accessible par la classe controller de ton framework
  • Faire en sorte que ton implémentation de repository utilises ton data store (via un ORM ou autre)

A partir de cette base, tu peux adapter selon le contexte et ton planning. Tu peux très bien montrer une fonctionnalité alors que tu n’as pas terminé la partie ORM et que tout se fait en mémoire :)… Sauf si tu dois déployer juste après !

En fait pour résumer, je pense qu’il faut bien isoler la partie fonctionnelle pour la tester intensément. Brancher l’IHM et la base de données, c’est de la plomberie. Moins de test sont nécessaires.

Ce qu’il faut éviter, c’est de faire reposer toute ta stratégie sur les tests système. Mais je pense que c’est OK pour toi.

@romain_touze, tu préfères créer un objet permettant d’effectuer toutes les actions liés au processus que tu créer avant de faire un test d’acceptation ? C’est intéressant mais je pense qu’il faut d’abord créer un code qui fonctionne et, ensuite, tu peux extraire la logique de ton contrôleur, et donc créer un objet comme le tien. Ça te permet d’être certain que l’objet que créer possède effectivement les bonnes fonctions.

A priori, les gens qui sont plus dans le BDD vont écrire tous les tests dès le début. Les gens qui sont plus TDD vont y aller test par test (pour des raisons qu’on pourra détailler dans un autre fil).

Je ne suis pas d’accord. Je pense que les gens qui font du BDD vont commencer par écrire un teste de très haut niveau puis descendre dans le code en créer un test à chaque étape. La seule différence est le fait d’écrire un test de haut niveau qui simule les actions d’un utilisateur.

Avec quel langage tu développes et sur quel plateforme ?

Bonne journée,
Guirec.

Juste pour clarifier une chose : qu’est-ce que faire du BDD (et inversement avec TDD) ? Quelles sont les différences avec le TDD ?

La BDD commence par un test de très haut niveau représentant le comportant de l’utilisateur. C’est la seule différence.

J’ajoute un point. Après avoir fait quelques recherches sur google, il s’avèrent que ma réponse n’était pas complète. Les tests de haut niveau sont un plus du BDD.

Une différence entre BDD et TDD est que dans le BDD tu va définir le comportement que dois avoir une fonction plutôt plutôt que son implémentation. C’est surtout une question de sémantique. Le BDD doit être plus facilement compréhensible par un humain. Par exemple, en BDD tu auras :

expect(sorted_list).to eq [1,2,3,4]

et en TDD :

assert_equal(sorted_list, [1,2,3,4])

Ce n’est pas une différence majeure mais la perspective change. Tu dois créer des fonctionnalités plutôt que simplement du code. De plus, tes tests peuvent servir de doc.

En effet c’est ce que j’avais cru lire aussi au fil de mes recherches mais n’ayant qu’une connaissance théorique je voulais avoir vos avis.

Pour un exemple complet de bout en bout, je te conseille le livre : Coder Proprement de Bob Martin
Les tests ne sont pas le seul sujet du livre, mais tu as des cas concrets.

1 « J'aime »

Le plus important pour moi c’est : Mieux vaut un test écrit après que pas de tests du tout.

Mais mieux vaut un test écrit avant qu’un test écrit après ;)
C’est pas une règle d’or bien sûr, mais les raisons d’écrire un test après sont rares.

1 « J'aime »

@guirec_corbel je pense qu’au final, on a le même résultat. Mais je pense qu’en commençant par mettre ta logique dans le controller tu vas potentiellement te trainer une dépendance au framework qui va te ralentir dans l’écriture des tests et ralentir leur exécution. Mon point de vue, c’est de mettre ton modèle métier dans un contexte bien cloisonné, sans dépendance à tout ce qui est framework et BDD et de concentrer l’effort de BDD / TDD là dessus. C’est un peu les principes poussés par le domain driven design.

L’infra, le framework, sont des éléments qui fonctionnent. Il y a moins besoin de les tester (on teste que le branchement).

Qu’est-ce que tu entends par test de très haut niveau en approche BDD ? C’est niveau vue ? Niveau controller ?
En BDD tu commences pas par d’écrire plusieurs use cases, ce qui te permet de visualiser ton avancement ?
(je ne suis pas très BDD mais il me semble que certains prennent cette approche). Après BDD et TDD, c’est quand même un peu la même chose je trouve.

L’approche que je présente peut être overkill sur une appli CRUD sans réelle logique métier. Mais on s’aperçoit aussi que faire du TDD dans ce genre de contexte c’est chiant, ça n’apporte pas grand chose.

Human Coders - Le centre de formation recommandé par les développeur·se·s pour les développeur·se·s