Accueil > .Net, Tests Unitaires > Tests Unitaires – Petit retour

Tests Unitaires – Petit retour

Aujourd’hui, ça ne sera pas réellement un billet technique, mais un billet un peu en forme de retour d’expérience (même si c’est encore récent), sur les tests unitaires.
Il y a des choses que l’on a raté, des choses que l’on a bien réussit et surtout des choses que l’on aurait voulu faire.

Du coup, le présent billet va décrire les différents éléments qui ont (auraient) été utiles pour la mise en place de nos tests.

 

Rappel du cadre.

 

Nous avons donc un certain nombre de batchs réalisant différents traitements.
L’avant dernière évolution avait requis trois semaines de charges à trois personnes pour tout tester et réaliser les tests de non régression (45 j/h, donc).

Du coup, quand nous avons réalisé la dernière évolution en date, on a voulu…faire autrement, pour ne pas essuyer les mêmes plâtres.

Dans un premier temps, il y a eu des tests basiques pour automatiser un peu les choses (à base de fichiers *.cmd et *.sql).
Ce fut un peu artisanal, mais ça permettait de faire tourner une trentaine de scénarios en une demi-journée (1/2 j/h, pour les mêmes tests).
L’inconvénient était que nous devions vérifier à la main les données.
Et comme l’erreur est humaine…

D’où le fait de vouloir basculer sur des tests unitaires.

 

Comment faire, avant de coder ?

 

Il ne faut pas oublier que comme pour tout code, il est utile de se poser un peu pour réfléchir.
Donc, voici quelques questions qui permettent de mieux voir clair avant de ce lancer.

Quel est le périmètre ?

 
Pour commencer les tests comme pour commencer tout code, il faut savoir ce que l’on va faire et donc tester.
Dire « on va tout tester » n’est pas viable dans un premier temps (d’autant plus que les tests ont un coût).
Donc, il faut délimiter un périmètre précis.

Dans notre cas, il y avait l’ajout d’un nouveau batch, donc le périmètre était défini de lui-même.
Cependant, nous avons quand même pris garde à vérifier les impacts sur l’existant, pour les TNR.
C’est seulement après que l’on se soit rôdé que les tests ont été élargis.

Quels sont les cas d’utilisation, le besoin ?

 
Partant du périmètre, il faut examiner tous les cas d’utilisation.
Le but, ici, est de dégager des scénarios de tests qui répondent aux besoins de l’utilisation.

Il faut donc partir des « cas qui fonctionnent » (un chef de projets référant technique me disait toujours : ce sont les « happy flows », comprendre les besoins minimaux pour que l’application soit utile).
On pourra ensuite les faire varier pour couvrir les différents messages d’erreur attendus.

Ici, on peut se baser à la fois sur les spécifications, le cahier de recette, sur les MOA ou même, luxe ultime, sur les utilisateurs.
Pour les derniers, c’est aussi un moyen de commencer à les impliquer dans les développements en leur prouvant que l’on veut livrer de la qualité et que pour ça, on a besoin d’eux (comme qui dirait : chacun son métier).
Dans le cadre d’un projet agile, c’est naturel, dans le cadre d’un cycle en V classique…et bien, ça permet éventuellement de « glisser » vers l’agilité !🙂

C’est un avis tout à fait personnel, mais pour moi, tester pour tester n’a pas de sens (comme coder pour aligner des lignes).
Je pense qu’il faut tester ce que doit permettre l’application (scénarios) et non le comment elle le fait (couverture de code, etc.).
Parce qu’au final, une application peut être super bien codée, commentée et tout et…ne pas répondre aux besoins.
Donc, il faut s’attacher aux besoins avant tout et tester en fonction.

Dans notre cas, il y avait quatre flux différents.
Chacun avait trois/quatre cas de base.
Après, chacun pouvait avoir des comportements supplémentaires.
Nous sommes repartit de la trentaine de tests existants (les *.cmd & *.sql).

L’un des avantages, aussi, de tester les scénarios, c’est que l’on se moque un peu de l’implémentation qui est en-dessous.
C’est à dire que si elle change (au hasard : refactoring, évolution…), le test n’a pas besoin d’être modifié : mieux, il pourra être le garant que la modification n’a pas introduite de régression (et ça, c’est bien).

Quels sont les points communs ?

 
Le but, ici, est de dégager tous les éléments communs dans les scénarios pour ne les implémenter qu’une seule et unique fois (ou du moins de faire du code suffisamment paramétrable pour les gérer).
Autrement dit, de faire un « template » de scénario.
C’est ce template qui va servir à conserver l’état du test.

Dans notre cas, nous avions toujours le déroulement qui suit :

  1. Insertion des données de tests.
  2. Pré-conditions (ex: modifier le paramétrage pour couvrir un cas différent).
  3. Lancement du batch.
  4. Récupération des données.
  5. Vérification des données.
  6. Post-conditions (ex: remise en place du paramétrage de base).
  7. Nettoyage des données.
  8. Export des données.
  9. Validation du test.

Dans ce cas, il est aisé de faire un template, avec des Action / Func pour injecter les modifications à apporter.

Isolation des scénarios

 
Un point très important : tous les tests doivent être isolés et indépendants.
Si un test influe sur un autre, c’est contre-productif car les effets pourront alors varier grandement dépendant de leur ordre ou tout simplement si l’on joue l’un sans l’autre.

Dans notre cas, j’ai finis par supprimer toute dépendance (je dis « finis », car il y en avait [ordonnancement, surtout] et je me suis rendus compte que c’était une TRÈS mauvaise idée).
J’avais un test qui plantait parfois dans une série, mais réussissait toujours quand je le jouais seul.
En fait, je me suis rendus compte que l’un de mes tests, s’il était exécuté juste avant le test posant problème occasionnait son plantage.
C’était un mauvais nettoyage de données qui m’a fait perdre beaucoup trop de temps.

 

Comment commencer à coder ?

 

Le premier cas de test.

 
Avec le point précédent, on a déjà une bonne base.
Il faut donc commencer à implémenter le template.

Après celui-là, il est plus aisé de créer le scénario le plus simple, celui avec le moins de choses à faire.
Il est aussi plus simple de faire un cas de test passant (happy flow) car on peut savoir à quoi s’attendre (et donc valider le code du test).
Il faut commencer petit pour monter doucement en charge (et accessoirement ne pas être découragé devant l’ampleur du travail).

Surtout, avant un test, il est très important d’écrire dans le code ce que le test est censé faire.
Il est simple de savoir ce que fait un test quand on travaille dessus, mais comme pour tout code, quand on y revient après deux, trois semaines…c’est dramatique.
Et encore pire quand le test est reprit par une autre personne.
Du coup, un pavé de commentaires pour expliquer brièvement ce qui est en entrée et le résultat attendu du test fera gagner pas mal de temps.

Quand on valide les données, il faut également être explicite.
C’est très frustrant de voir un test échoué sans en connaître la raison.
Il faut alors débuguer le test en question en pas-à-pas pour savoir où ça plante et éventuellement pourquoi.
Du coup, mettre des Assert quand le test ne doit pas aller plus loin (exemple : lorsque l’on insère les données du test en base, mettre un Assert qui fait échouer le test en cas de plantage), c’est un bon début.
Après, il peut être pertinent d’ajouter un système de log (que ce soit un outil externe ou fait à la main) afin de surveiller le déroulement du test (savoir à quel étape le test à planté, avec des informations additionnelles).

Avec ces deux éléments, il est plus aisé de savoir où un test échoue et pourquoi.
En somme, c’est comme pour une application que l’on met en recette : le plus de trace possibles pour avoir le plus d’informations possible avant de creuser.
Sinon, on en revient au recetteur qui dit « ça a planté ».

Et les suivants…

 
En ce qui concerne la mutualisation du code…
A l’inverse d’une application qui va aller en production, je suis plus mitigé sur ce point.
Surtout en ce qui concerne la création des données de test.

J’ai eu à faire face à un problème très simple : un test qui devait passer et qui plantait.
Soit, ça arrive.
Après vérifications, le test en lui-même était correct.
Résultat, c’était les données qui étaient incorrectes… Et là, c’est le drame. Parce qu’il faut retrouver LE champs qui n’a pas la bonne donnée… (et comme je ne maîtrise pas encore à 100% le fonctionnel…).
J’ai perdu un temps pas croyable sur une valeur à la con comme ça…

Résultat, mes créations de données sont réduites à leur plus simple expression pour le tronc commun et la majeure partie déléguée au test.
Ça fait plus de code, c’est un peu plus long à maintenir, mais j’ai plus de granularité, de flexibilité et moins de source d’erreurs possibles (moins de maux de tête, aussi…).
Et accessoirement, ça me permet de décrire fonctionnellement à quoi va servir le champ en question.

Valider son test.

 
La dernière étape est la validation du test.
C’est une étape TRÈS importante car c’est là où la plus-value du test unitaire se fait sentir le plus.

Dans mon cas, j’ai choisis de faire des validateurs très, très, très unitaires. Mais du coup, il ont une ré-utilisabilité très forte.
Exemple 1 : tester le code retour du batch.
Exemple 2 : il y a une table où l’on insère les différentes étapes du batch. Donc, je donne la liste ordonnée des codes attendus et la validateur la compare (dans les deux sens) à la liste des code obtenus.
Exemple 3 : est-ce que le fichier supposément créé par le batch est bel et bien créé ?

Ici, la granularité de la validation peut être très profonde.
Plus les validateurs seront poussés (et leurs erreurs éventuelles remontées), plus il sera aisé de voir ce qui pose problème, de diagnostiquer et donc de résoudre les anomalies.

 

Conclusion.

&nsbp;

Ce billet ne fonctionne pas tout seul et s’insère dans mes autres billets traitant des tests unitaires (j’en ai encore en brouillon…).
Le lire tout seul n’est donc pas forcément pertinent, mais il donne un bon aperçu de comment nous avons procédés et, au final, assez bien réussis.
La prochaine étape (je ne sais quand, je n’ai pas la main dessus) sera l’intégration continue avec TFS et, pourquoi pas, plus tard, un vrai TDD.

En tout cas, pour notre bilan.
Nous avons élargis les tests à l’existants, ce qui nous a permit de réaliser des études (certaines parties de code étant en prod sans évolution depuis plus de quatre ans, la connaissance s’est évaporée) et des tests de non régressions. Nous avons également pu avoir une application livrée en recette sans qu’il n’y ai aucun retour (oui, oui, aucun bug).

Honnêtement, je suis quand même assez fier, pour le coup. Déjà parce que j’ai bossé pas mal de temps en solo dessus et ensuite parce que les tests unitaires nous ont permis de livrer une application fiable et correspondant aux attentes. Dernier point important, la hiérarchie (qui a bien voulu nous laisser faire) est intéressée.
Que du bonheur, donc😀

Catégories :.Net, Tests Unitaires
  1. Aucun commentaire pour l’instant.
  1. 06/02/2013 à 16:17

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :