Affichage des articles dont le libellé est ATDD. Afficher tous les articles
Affichage des articles dont le libellé est ATDD. Afficher tous les articles
Tour de Babel : Atelier en plusieurs langues
J’ai animé aujourd’hui un atelier où les participants parlaient plusieurs langues. Il y avait un américain, un canadien, une brésilienne, un allemand et plusieurs français.
D'habitude, dans ce cas, la langue utilisée est l’anglais, mais aujourd’hui, c’est le français qui a été utilisé, Oui!
Tous ont fait l’effort de parler en français. Le canadien avait un peu de mal, l’américain lui faisait la traduction lorsque nécessaire.
Il s’agissait d’un atelier de début de projet, nous avons identifié les personae.
Premièrement, j’étais étonné que pour une fois, nos amis d’outre atlantique fassent l’effort de parler notre langue, même avec difficulté, c’était un joli message de collaboration avec l’équipe.
Au-delà de la langue et de la sémantique, ce qui m’a interpellé c’est qu’au travers du « peu » de vocabulaire de nos collègues étrangers, c’est le besoin qui était challengé. A chaque mot utilisé, en cas de doute, tout le monde cherchait la traduction dans la langue originale de chacun. Une sorte de règle des 3C adaptée aux mots. Card : le mot, Conversation : la bonne traduction, donc compréhension, Confirmation : tout le monde est d’accord.
Ce qui est marrant aussi, c’est que par habitude les français lançaient leurs phrases en anglais, puis en milieu de phrase, s’en rendant compte, la refaisaient en français.
J’ai trouvé ça vraiment très instructif. Ça change finalement des réunions en franglais, où nous n’osons pas dire à nos interlocuteurs anglophones que nous avons compris le contexte, mais pas forcément les détails.
Autre point que j’ai noté, l’utilisation des personae renforce le côté spécification par l’exemple. A partir du moment où un acteur du système est personnifié, ces activités le sont aussi par des jeux d’exemple, de micro scénarios. Il a été beaucoup plus facile de leur faire écrire les histoires utilisateur ensuite.
A-TDD avec Robot Framework
A-TDD with Robot Framework de Craig Larman et Bas Vodde.
Traduction disponible ici.
Original disponible sur le site de RobotFramework.
Traduction disponible ici.
Original disponible sur le site de RobotFramework.
RobotFramework EclipseLibrary for Java/SWT
Nouvelle version de la librairie EclipseLibrary pour Robotframework
Cette nouvelle version permet, entre autres corrections, de tester des applications Java utilisant SWT en lieu et place de Swing.
Elle reste basée sur l'outil SWTBot et reprend les mêmes mots clés que ceux des applications Eclipse/RCP.
Avec un lanceur spécifique pour application Java repris de la librairie SwingLibrary.
Le lanceur Eclipse a aussi été remanié pour mieux supporter les erreurs de démarrage d'Eclipse.
Toutes les infos sont là!
Robotframework supporte Flex/Falsh
A l'aide de la librairie SeleniumLibrary 2.6, Robotframework, l'outil de test d'acceptance, supporte maintenant Flex/Flash
Les concepts:
http://code.google.com/p/
La Release Note:
http://code.google.com/p/
La démo :
http://code.google.com/p/
Ant pour Robotframework
Robotframework, l'outil de test d'acceptance, a maintenant la possibilité d'être lancé depuis un script Ant.
La première version de la tâche <robotframework> est disponible.
Elle se base sur la tâche <java>
Retrouver toutes les infos sur robotframework-ant
Craig Larman and Bas Vodde on RobotFramework
Article issu du livre "Practices for Scaling Lean & Agile Development" des mêmes auteurs.
A lire sur
http://code.google.com/p/robotframework/wiki/ATDDWithRobotFrameworkArticle
RobotFramework pour Eclipse 0.10 disponible
- Utilisation du format standard RF pour le temps, ex : 30 s au lieu de 30000
- Correction et amélioration du "finder" pour les types Tree and tree item.
- Nouveau mot-clé pour le type List : getAllItems
- Correction des mots-clés Wait et Until.
- Ajout gestion de la préférence DEFAULT_POLL_DELAY.
- Correction et amélioration du "finder" pour les types Tree and tree item.
- Nouveau mot-clé pour le type List : getAllItems
- Correction des mots-clés Wait et Until.
- Ajout gestion de la préférence DEFAULT_POLL_DELAY.
User Stories, ATDD et BDD
User Story:
Une user story est une façon de « spécifier » un besoin fonctionnel.
C'est au-delà de l’aspect écrit d’une spécification, surtout un moyen de communiquer entre le Product Owner (ou analyste métier) et l’équipe de développement. Je vous renvoie à l’article de Martin Fowler à ce sujet.
Une user story est exprimée en utilisant la phrase suivante :
En tant que « rôle », je veux « faire une action », afin d’ « atteindre un objectif »
Il est important de définir le rôle, l’action, mais aussi l’objectif à atteindre. Le fait de réfléchir à ces trois éléments permet de préciser son besoin mais aussi son pourquoi : Un point important pour l’estimation de la valeur ajoutée de la user story.
Granularité.
Plus la granularité d’une user story est fine, plus l’implémentation en sera simplifiée. Voir même, influencera l'architecture de la solution et améliorera l'aspect unitaire et autonome du code produit.
Une user story a une granularité en fonction de sa maturité.
Je prends pour exemple la user story suivante :
« En tant qu’utilisateur, je veux me connecter à google afin d’accéder à tous mes services en lignes »
A elle seule, cette user story représente (presque) tout ce que google m’apporte.
Bill Wake propose une méthode d’estimation du bien fondé d’une user story : INVEST
ATDD:
Mais comment passer d’un besoin exprimé en langage métier en un produit exprimé en langage de programmation. (Il y a eu UML, un temps…)
L’approche ATDD ou Acceptance Test Driven Development
C’est d’une certaine manière la faculté de renvoyer l’envoyeur à sa responsabilité de clairement préciser son besoin par la définition des critères de contrôles qui lui serviront à vérifier l’adéquation du produit au besoin.
Il s’agit de compléter la user story par des critères d’acceptance.
Reprenons l’exemple :
« En tant qu’utilisateur, je veux me connecter à google afin d’accéder à tous mes services en lignes »
Imaginons les critères suivants :
Premier élément de réflexion :
Un critère d’acceptance doit être :
J’utilise cette méthode d’habitude allouée aux tâches pour les critères d’acceptance.
Un critère d’acceptance doit avoir les caractéristiques suivantes :
Voilà, j'ai fait le tri.
C’est un exemple bien sur, et je n’ai pas le moyen de communiquer avec les équipes de développement Google pour qu’ils me fassent part de leurs remarques afin d’améliorer l'exhaustivité et raffiner cette liste de critères.
BDD :
J’ai une user story et des critères d’accetance. Maintenant, comment spécifier le comportement de l’application attendu pour répondre au besoin.
A ce stade, j’utilise le langage Gherkin défini pour le BDD ou Behavior Driven Developement : Given / When / Then (And)
Ce langage permet de spécifier les scénarios utilisateurs, le comportement de l’application.
La user story s'exprime ainsi au final :
« En tant qu’utilisateur, je veux me connecter à google afin d’accéder à tous mes services en lignes »
Scénario 1 : Accéder à la page de connexion
Given L’utilisateur n’est pas connecté
And L’utilisateur est sur la page d’accueil google
When L’utilisateur demande à se connecter
Then La page de connexion est affichée
Scénario 2 : Se connecter
Given L’utilisateur n’est pas connecté
And L’utilisateur est sur la page de connexion
When L’utilisateur saisi son identifiant
And L’utilisateur saisi son mot de passe
And L’utilisateur clique sur « Connexion »
Then La page d’accueil google s’affiche en <30s
And L’identifiant de l’utilisateur est affiché à droite dans le bandeau
Je n'ai volontairement pas traduit Given / When / Then (And).
Le langage gherkin est interprété par de plus en plus d’outils de test (robotframework, cucumber, fitnesse, …).
Il suffit, dans ce cas, de simplement copier/coller les scénarios dans votre outil préféré de test. Ne reste plus qu’à implémenter les keywords ou autres fixtures. Robotframework pour les tests IHM, cucumber pour les tests unitaires par exemple.
N.B.: Cet article m’a été fortement inspiré par les travaux de Raj Mudhar au sujet de l’ATDD
Et aussi du tutoriel BDD sur robotframework proposé par Yannick Ameur
Une user story est une façon de « spécifier » un besoin fonctionnel.
C'est au-delà de l’aspect écrit d’une spécification, surtout un moyen de communiquer entre le Product Owner (ou analyste métier) et l’équipe de développement. Je vous renvoie à l’article de Martin Fowler à ce sujet.
Une user story est exprimée en utilisant la phrase suivante :
En tant que « rôle », je veux « faire une action », afin d’ « atteindre un objectif »
Il est important de définir le rôle, l’action, mais aussi l’objectif à atteindre. Le fait de réfléchir à ces trois éléments permet de préciser son besoin mais aussi son pourquoi : Un point important pour l’estimation de la valeur ajoutée de la user story.
Granularité.
Plus la granularité d’une user story est fine, plus l’implémentation en sera simplifiée. Voir même, influencera l'architecture de la solution et améliorera l'aspect unitaire et autonome du code produit.
Une user story a une granularité en fonction de sa maturité.
Je prends pour exemple la user story suivante :
« En tant qu’utilisateur, je veux me connecter à google afin d’accéder à tous mes services en lignes »
A elle seule, cette user story représente (presque) tout ce que google m’apporte.
Bill Wake propose une méthode d’estimation du bien fondé d’une user story : INVEST
ATDD:
Mais comment passer d’un besoin exprimé en langage métier en un produit exprimé en langage de programmation. (Il y a eu UML, un temps…)
L’approche ATDD ou Acceptance Test Driven Development
C’est d’une certaine manière la faculté de renvoyer l’envoyeur à sa responsabilité de clairement préciser son besoin par la définition des critères de contrôles qui lui serviront à vérifier l’adéquation du produit au besoin.
Il s’agit de compléter la user story par des critères d’acceptance.
Reprenons l’exemple :
« En tant qu’utilisateur, je veux me connecter à google afin d’accéder à tous mes services en lignes »
Imaginons les critères suivants :
- L’utilisateur peut se connecter
- La barre de menu google présente les services disponibles
- L’utilisateur peut accéder à tous ces services
- Google trace la connexion de l’utilisateur
- Google présente la liste des nouvelles fonctionnalités disponibles
Premier élément de réflexion :
Un critère d’acceptance doit être :
- Une vision utilisateur
- Ne pas proposer de solution
- Ne pas être interne à la fonction
- Google trace la connexion de l’utilisateur. C’est un traitement interne à la fonction, l’utilisateur n’est pas informé. Cela pourrait faire l’objet d’une autre user story du type En tant qu’administrateur google, je veux être informé de toute connexion, afin d’analyser le trafic sur le site
J’utilise cette méthode d’habitude allouée aux tâches pour les critères d’acceptance.
Un critère d’acceptance doit avoir les caractéristiques suivantes :
- Specific = défini et explicite
- Measurable = quantifiable, observable
- Achievable = peut exister, peut être fait
- Relevant = approprié à cette user story
- Time bound = quand cela doit-il intervenir
| Criteria | Specific | Measurable | Achievable | Relevant | Time Bound |
| L’utilisateur peut se connecter | Acceder à la page de connexion Saisir son identifiant et son mot de passe et demander la connexion | L’utilisateur voit son identifiant à droite dans le bandeau google | OUI | OUI | <30s |
| La barre de menu google présente les services disponibles | On a la barre de manu même non connecté | | | N | |
| L’utilisateur peut accéder à tous ces services | C’est spécifique par service | | | N | |
| Google présente la liste des nouvelles fonctionnalités disponibles | Message « Découvrez nos nouvelles fonctionnalités » | Message affiché en haut à droite | OUI | N | ce n’est pas le fait de se connecter qui déclenche ce message |
Voilà, j'ai fait le tri.
C’est un exemple bien sur, et je n’ai pas le moyen de communiquer avec les équipes de développement Google pour qu’ils me fassent part de leurs remarques afin d’améliorer l'exhaustivité et raffiner cette liste de critères.
BDD :
J’ai une user story et des critères d’accetance. Maintenant, comment spécifier le comportement de l’application attendu pour répondre au besoin.
A ce stade, j’utilise le langage Gherkin défini pour le BDD ou Behavior Driven Developement : Given / When / Then (And)
Ce langage permet de spécifier les scénarios utilisateurs, le comportement de l’application.
La user story s'exprime ainsi au final :
« En tant qu’utilisateur, je veux me connecter à google afin d’accéder à tous mes services en lignes »
Scénario 1 : Accéder à la page de connexion
Given L’utilisateur n’est pas connecté
And L’utilisateur est sur la page d’accueil google
When L’utilisateur demande à se connecter
Then La page de connexion est affichée
Scénario 2 : Se connecter
Given L’utilisateur n’est pas connecté
And L’utilisateur est sur la page de connexion
When L’utilisateur saisi son identifiant
And L’utilisateur saisi son mot de passe
And L’utilisateur clique sur « Connexion »
Then La page d’accueil google s’affiche en <30s
And L’identifiant de l’utilisateur est affiché à droite dans le bandeau
Je n'ai volontairement pas traduit Given / When / Then (And).
Le langage gherkin est interprété par de plus en plus d’outils de test (robotframework, cucumber, fitnesse, …).
Il suffit, dans ce cas, de simplement copier/coller les scénarios dans votre outil préféré de test. Ne reste plus qu’à implémenter les keywords ou autres fixtures. Robotframework pour les tests IHM, cucumber pour les tests unitaires par exemple.
N.B.: Cet article m’a été fortement inspiré par les travaux de Raj Mudhar au sujet de l’ATDD
Et aussi du tutoriel BDD sur robotframework proposé par Yannick Ameur
Are you ready-ready to be done-done
Jeff-Sutherland, continuant l’agilisation de Systematic, présente une évolution du concept de done/done : le ready/ready
Ou plus exactement la Feature ready/ready checklist.
A partir de la notion de done/done, normalement, une équipe construit une liste de typologie de tâche ayant pour objectif l’atteinte du done. Cette checklist se place au niveau Scrum Team, côté développement donc.
La checklist ready/ready reprend ce concept et le remonte d’un cran, côté demandeur (Product Owner, Architecte, et lead developper).
En clair, quelles sont les tâches de préparation nécessaires au passage d'une feature dans l'état "Not Started" de ces user stories dans le task board (repris du Kanban Lean) :
• Preparation de la feature pour l’engagement : préparation à l’acceptation
• Clarification de la feature pour l’équipe : prise en compte des contraintes de l’équipe de développement en amont
• Préparation de la feature pour implémentation : passage de la feature aux stories pour préparation aux sprints.
Tous ces points sont fortement conditionnés par la mise en place du pilotage par les tests d’acceptance (ATDD)
Deux métriques sont utilisées :
• Le Process Efficiency
S’il faut une journée idéale pour produire une user story mais qu’en fait elle est produite en 5 jours, alors l’efficacité est de 20%. Le but étant d’augmenter cette efficacité en préparant au mieux l’implémentation, la checklist ready/ready est construite en ce sens.
• Fix time after failed builds
De la même manière, quelle est l’efficacité du processus concernant la correction d’une erreur de build. Ici, les pratiques ATDD permettent la réduction de temps.
Plus que le nombre de bugs, c’est bien le temps de correction qui importe.
Ou plus exactement la Feature ready/ready checklist.
A partir de la notion de done/done, normalement, une équipe construit une liste de typologie de tâche ayant pour objectif l’atteinte du done. Cette checklist se place au niveau Scrum Team, côté développement donc.
La checklist ready/ready reprend ce concept et le remonte d’un cran, côté demandeur (Product Owner, Architecte, et lead developper).
En clair, quelles sont les tâches de préparation nécessaires au passage d'une feature dans l'état "Not Started" de ces user stories dans le task board (repris du Kanban Lean) :
• Preparation de la feature pour l’engagement : préparation à l’acceptation
• Clarification de la feature pour l’équipe : prise en compte des contraintes de l’équipe de développement en amont
• Préparation de la feature pour implémentation : passage de la feature aux stories pour préparation aux sprints.
Tous ces points sont fortement conditionnés par la mise en place du pilotage par les tests d’acceptance (ATDD)
Deux métriques sont utilisées :
• Le Process Efficiency
S’il faut une journée idéale pour produire une user story mais qu’en fait elle est produite en 5 jours, alors l’efficacité est de 20%. Le but étant d’augmenter cette efficacité en préparant au mieux l’implémentation, la checklist ready/ready est construite en ce sens.
• Fix time after failed builds
De la même manière, quelle est l’efficacité du processus concernant la correction d’une erreur de build. Ici, les pratiques ATDD permettent la réduction de temps.
Plus que le nombre de bugs, c’est bien le temps de correction qui importe.
Robotframework pour Eclipse: nouveau starter
Cette nouvelle version change complètement le mode de lancement d'Eclipse, puisqu'il utilise maintenant le launcher Eclipse equinox. Assurance d'une meilleur prise en charge du chargement des bundles.
Robotframework pour Eclipse

Et voilà, après quelques nœuds au cerveau et de belles prises de têtes, j'ai mis à disposition une librairie de tests automatisés type ATDD d'application RCP / SWT sous Eclipse pour Robotframework.
Basé sur SWTBot, robotframework-eclipselibrary fait le pont entre robotframework et ce dernier.
EclipseLibrary reprend les même principes que SWTBot en les transposant sous forme de mots-clés pour RobotFramework.
Vous pouvez retrouver la liste complète des mots clés disponibles ici.
Pour l’instant c’est une « release candidate » comme on dit, en clair, il doit certainement rester des bugs quelque part.
Toutes les instructions d’installation, les concepts et la doc sont disponibles sur le site robotframework-eclipselibrary.
Un user group est disponible aussi.
PS : Un grand merci à Eric Rubinat pour son aide précieuse sur Eclipse et Bruno Marchesson pour ce qui est de l’introspection Java.
Strategie de test
Ma démarche de test
V1.0
© Laurent Carbonnaux, 12 Mai 2010
Sommaire
Préambule
Les TU c’est bien, le TDD c’est mieux, TDR c’est très bien
TU sans TDD sans TDR c’est mieux que rien.
Sans test d’acceptance côté interface utilisateur (IHM, ligne de commande, …), c’est déjà bien.
Bon, une fois qu’on a dit ça, on fait un joli plan de test qui sera relu par les ingénieurs qualité et tout ça, et tout ça.
Mais le développeur, lui, il est un peu embêté.
D’abord, je suis à peu prés sûr qu’il ne lira pas le plan de test; Si, si, ça arrive, et en plus il s’en fou !
Le développeur, il sait ce que c’est que d’écrire un test, enfin, il sait, il sait ce qu’il doit coder pour écrire un test. Mais sait-il ce qu’il faut tester ?
Il lui faut de l’aide, une bonne stratégie de test.
NB : Je n’ai pas la prétention de répondre à tout, encore moins d’avoir la science infuse, mais quelques expériences à partager. Je préfère donc parler de démarche plutôt que de stratégie.
Remerciements : Merci à Julien Patou (People In Action) qui a jouer le rôle de Product Owner et David Brocard celui de critique constructive, tous deux membres de l’association SigmaT.
Ma démarche de test
Stratégie de test : Vaste sujet, qui va des tests unitaires à la recette du produit.
C’est souvent comme cela qu’on l’aborde dans les formations ou les séminaires : par petit bout et dans ce sens.
J’ai donc voulu ici faire le chemin inverse et revenir à l’origine : le besoin du client.
Certes, l’approche cycle en V répondait (tiens, pourquoi je mets ça au passé ?) d’une certaine façon à cela, mais ne fournissait pas de réelle réponse, ni de garantie de cohérence en traversant les différentes couches d’intervenants : la branche de gauche qui décompose le besoin du client jusqu’au code (voir mon billet « Exigences, validation et traçabilité »)
Une stratégie de test, C’est quoi :
- Définir clairement ce qui doit ou pas être testé et comment, quand, par qui.
- L’éternelle roue de Deming : Plan-Do-Check-Act que l’on utilise comme une fractale.
- Au delà des métriques. Ça n’a aucun sens de demander une couverture à 70%. Que fait-on des 30% restant, à quoi correspondent-ils, comment choisir ce qui est dans les 30 ou dans les 70 ?
- Autre approche, on teste tous ce qui est de type DAO, classes de service, classes métiers, classes outils. Cette approche est déjà plus efficace dans l’aide au choix.
- C’est aussi trouver le bon niveau de test entre coût de développement et retour sur investissement
- Faire un test est couteux, mais il faut le ramener au nombre de fois où l’on pourra le jouer. Avec une contrainte de temps et de budget comme imposé par les contrats au forfait, il n’est inimaginable de TOUT tester. Certaines exigences seront plus couteuses à tester que d’attendre le bug et de le corriger (là, ça va pas plaire, mais c’est la dur réalité des projets), pour la simple et bonne raison, qu’il sera traité dans un autre budget (garantie, tma) et un autre temps.
- Là encore, il va donc falloir aider l’équipe à choisir.
M’inspirant des travaux de Jean-Michel Inglebert qui apprend les méthodes de tests unitaires (TDD sans le dire) à ces élèves, j’ai voulu par un exemple très simple montrer ce qui peut/doit se faire. (sa prez à l’Agile Tour 2009).
Jean-Michel Inglebert explique les tests unitaires de la façon suivante :
« Les plus simples et pourtant il s’agit …
- d’oublier les approches manuelles
- d’expliciter les limites
- de traiter le qualitatif et le quantitatif
et Ce qu’il faut retenir …
- C’est irréalisable par un humain
- Les limites sont aussi importantes que les cas standards pour l’utilisateur de la librairie
- Le retour sur investissement est important : l’information fournie sur la fonction versus le volume du code de test
«
Au final, les tests unitaires servent à :
- Garantir le besoin au niveau « mécanique » : qualitatif et quantitatif
- Fournir un moyen de contrôle de stabilité : tests de non régression
- Définir les limites d’utilisation : mieux que de la spec détaillée
A mon sens, la seule vraie façon de garantir la maintenance du système, bien au delà de n’importe quelle documentation.
Reprenons
A partir de cela, il nous est :
- impossible de tout tester
- il y aura donc des bugs
Deuxième objectif induit par cette contrainte, ne pas perdre de temps à trouver la source du bug.
Coupler deux stratégies, détaillées plus loin :
- tests en couches
- tests automatiques sur résultat de référence
En cas de bug, cette double stratégie permet de :
- être alerté d’un changement de comportement, ce n’est pas encore un bug. Il faut vérifier alors si c’est l’application qui n’est pas cohérente ou le test (ou le résultat de référence) qui n’a pas évolué.
- de savoir quelle couche l’a découvert. Par exemple, si le bug est découvert sur les tests d’IHM, mais pas par les tests unitaires, normalement le bug est au niveau de l’interaction entre le code de gestion et l’interface utilisateur (MVC)
Tests en couches
- Les tests unitaires s’occupent de vérifier le code.
- Les tests IHM, testent l’interaction utilisateur avec ce code.
- Les tests fonctionnels ou d’acceptance testent la conformité aux besoins (exigences fonctionnelles) du client, au cahier des charges.
- Les tests d’intégration n’interviennent que s’il on a de l’intégration entre sous systèmes. Pas entre composants, c’est du niveau tests unitaires.
L’objectif est que chaque couche de tests s’appuie sur celle du dessous et ne cherche pas à re-tester la même chose, mais compléter la cohérence global. Comme je l’expliquais dans un précédent billet.
Je ne parle pas ici de dichotomie tests/fonctionnels / tests unitaires, résultant d’une décomposition des tests, mais bien de la notion de complémentarité.
Il faut bien prendre en compte l’objectif et les limitations des outils utilisés.
Par exemple, l’interaction homme/machine ne sera pas testé avec du JUnit, et un algorithme mathématique avec du Sélénium. Par contre, garantir que les paramètres saisis par l’utilisateur sont bien transmis à l’algorithme interne par du Sélénium avec un jeu de données simple et tester plus largement l’algorithme avec JUnit en faisant varier les paramètres entre leurs bornes, voir même à l’extérieur pour tester la robustesse.
Les tests d’intégration sont un peu à part : Traversant les couches décrites ci-dessus, mais entre sous systèmes, il leur faudra aussi vérifier
- la mécanique : des tests unitaires pour la couche technique d’échange, objectif: Contrôler le modèle échangé
- l’interface métier : vérifier qu’au-delà du modèle d’échange, les deux sous systèmes l’interprètent de la même manière
- et enfin le fonctionnel, c’est l’aspect utilisateur du système complet, qui ne s’intéresse qu’à la partie métier pure, ce que l’utilisateur va faire avec son système….
Tests automatiques sur résultats de référence
Il est souvent difficile d’obtenir des données réelles de la part de nos clients. Soit elles sont confidentielles, soit pas encore existantes, mais simplement en partie spécifiées.
Le but du jeu est de définir un jeu de données (entrée/sortie) dit de référence et d’automatiser l’exécution du test qui nous alertera si, avec les mêmes entrées, les sorties sont différentes de la référence.
Ces données de références peuvent être validées en parallèle avec les experts concernés.
En attendant, je ne perds pas de temps !
Cette pratique est très utile dans le cas d’appel de composant « boite noire » (type moteurs de calcul) fournis par le client.
Où l’on a du mal à faire la différence entre tester le code d’appel du composant et le composant lui-même. Du moins est-il difficile en cas d’erreur de savoir si elle provient du code appelant ou du composant. La frontière est délicate.
Dans ce cas, j’utilise les tests par référence.
Je joue le test une fois avec des données d’entrée, je récupère les données de sortie et ces jeux de données deviennent la référence. Le code appelant est testé unitairement, et la référence sera contrôlée et validée ensuite.
En exécution automatique, je serai alerté en cas de résultat différent de la référence.
Est-ce le composant qui a changé ? Le code d’appel ? Le code traitant les résultats ? Je ne sais pas directement, il faut investiguer un peu plus, mais au moins je suis alerté du changement de comportement de l’application autour de cette fonction.
Le Jeu
J’ai demandé aux membres de l’association SigmaT de me définir une ou deux User Stories. Je suis resté assez vague sur l’objectif, j’ai juste précisé que c’était pour faire un topo sur les tests.
Je m’y attendais, les premières réponses étaient des questions, ou des propositions techniques !
Premier constat : Nous, informaticiens, ne sommes pas faits pour définir les besoins, mais pour y répondre, trouver des solutions.
J’ai reçu plusieurs propositions de user stories, j’ai choisi la suivante qui correspondait le mieux à mon besoin de simplicité. Je croyais !
Premier essai :
ID : | US1 |
Nom : | Créer un nouvel utilisateur |
Description : | En tant que : * ADMINISTRATEUR (utilisateur ayant des droits d'administration) Je peux : * créer un utilisateur Afin de : * insérer un utilisateur dans le référentiel de données |
Critères d’acceptance : | * Vérifier que seul un utilisateur ayant le droit d'administration à accès à l'écran de création d'un utilisateur * Vérifier que les champs de saisie "login" et "mot de passe" sont obligatoires (le bouton "Créer" n'est actif que si les champs ne sont pas vides) * Vérifier que les champs de saisie "login" et "mot de passe" respectent le format spécifié (le bouton "Créer" n'est actif que si les champs "login" et "mot de passe" contiennent respectivement au moins 4 et 6 caractères) * Vérifier que si un utilisateur avec le login saisi existe déjà en base un message d'avertissement s'affiche : "Un utilisateur avec cet identifiant existe déjà" * Vérifier qu'après l'action de création, un message d'information s'affiche : "Utilisateur créé avec succès" * Vérifier qu'après l'action de création, l'écran de création d'un utilisateur est vierge * Vérifier qu'après l'action de création, un enregistrement est présent dans la table USER avec les attributs saisis et la date du jour dans le champ CREATION_DATE |
A partir de là, oups ! Premier problème, je fais ça en quoi ?
Problème de technicien, pas le sujet du jour, au plus simple. D’autant que je cherche à préciser une stratégie, pas forcément les pratiques elles-mêmes.
Est-ce qu’un TDD en swing est si différent qu’en web ou .Net, dans la technique oui, les outils oui, mais le principe non.
Deuxièmement, je commence par quoi !
Tout simplement, mais c’est très bête, lire le sujet et préparer les moyens de tests.
Analyse du besoin fonctionnel pour définir les besoins de tests :
- pouvoir identifier l’utilisateur de l’appli. Il manque une user story précisant qu’il faut se signer sur l’appli pour tester le premier critère, donc on teste mais on bouchonne. Mais en même temps la user story ne précise pas comment l’utilisateur fait pour accéder à l’écran de création, un bouton, un lien, un url ??? Dans ce cas, il faut demander précision au product owner, normalement lors du workshop ou pendant le sprint, dans ce cas, on ajoute une exigence à la story.
- Réponse :
Compléments | - un bouton (ou lien) "Nouvel utilisateur" inactif (ou invisible, encore mieux) pour ceux qui n'ont pas les droits. On n'informe pas la personne qu'elle n'a pas les droits, ça peux la vexer :-) . |
- Il faut un écran de base d’où sera exécuté l’action et un écran de création d’un utilisateur.
- Une base de données avec une table USER avec champs login, motdepasse et creation_date. (Ce n’est pas un besoin fonctionnel, mais le client le demande, alors !)
Analyse du besoin fonctionnel pour définir les scénarios de tests : Normalement en TDR
- test si utilisateur est admin alors écran création utilisateur dispo (bouton « Nouvel utilisateur» actif)
- test si utilisateur pas admin alors bouton « nouvel utilisateur » invisible
1er partie, décrire les scénarios de tests d’acceptance.
Pour cela j’ai utilisé RobotFramework. Pour deux raisons :
- C’est un outil de test dit « keyword driven ». Hormis une phase d’apprentissage de l’outil, il ne nécessite aucune compétence de développement.
- Il permet de tester l’interaction avec le sous système. Les IHMs : de type web couplé avec Sélenium, ou de type java swing. Les process simples, ou telnet, ssh…
- Et de 3, parce que j’en ai eu le besoin, on peut facilement ajouter des keywords de base, cela nécessite un peu de développement.
Deux scénarios : Lancer l’appli en deux modes. 1 mode admin, 1 mode non admin, 1er critère oblige.
Scénario en mode non admin.
N’ayant pas encore de « modèle » pour gérer les droits de l’utilisateur, ni de gestion de la connexion utilisateur, j’ai simplement utilisé une méthode « isUSerAdmin() » qui retourne un booléen. Cette méthode servant de bouchon pourra être facilement remplacé lorsqu’on s’attaquera à la bonne user story. En attendant, je lance l’appli en mode avec paramètre pour simuler le mode admin, sans paramètre en mode non admin.
Scénario :
- Lancer l’application en mode non admin
- Sélection de la fenêtre principale
- Vérifier que le bouton « Nouvel Utilisateur » n’est pas visible (c’est ce keyword que j’ai du ajouter)
Ça donne dans RobotFramework :
Test Mode Non Admin
Test Case | Action | Arguments | ||
[Setup] | Lancer appli en mode non Admin | |||
Button Should Not Be Visible | ${boutonNewUser} | |||
Keyword | Action | Arguments | ||
Start Application | org.lcx.myapp.MySampleApp | |||
Select Window | ${AppName} | |||
Scénario en mode admin.
Scénario :
- Lancer Appli En Mode Admin
- Lancer l’application en mode admin
- Sélection la fenêtre principale
- Ouvrir Ecran Creation Utilisateur
- Vérifier que le bouton « Nouvel Utilisateur » est actif
- Cliquer sur le bouton « Nouvel Utilisateur »
- Vérifier que l’écran est ouvert
- Les Champs Sont Obligatoires
- Vérifier que le bouton créer est inactif
- Saisir 4 caractères dans le login et 6 dans le password
- Vérifier que le bouton créer est actif
- Vider les champs login et password
- Vérifier que le bouton créer est inactif
- Les Champs Sont Au Bon Format
- Vérifier que le bouton créer est inactif
- Saisir 3 caractères dans le login et 5 dans le password
- Vérifier que le bouton créer est toujours inactif
- Saisir 4 caractères dans le login et 6 dans le password
- Vérifier que le bouton créer est actif
- Ce test n’est pas suffisant, il faudra un test unitaire pour compléter le contrôle de la règle de gestion
- Créer Un Utilisateur
- Saisir 4 caractères dans le login et 6 dans le password
- Cliquer sur le bouton créer
- Vérifier l’affichage du message de création
- Fermer le message
- Vérifier que les champs login et password sont vides.
- Vérifier que les données existent en base dans la table User. (je n’ai volontairement pas testé la date)
- Créer Un Utilisateur en double
- Re-saisir le même login et password
- Cliquer sur le bouton créer
- Vérifier le message d’erreur
- Fermer le message
- Supprimer les données de la table User
Test Fonctionel Mode Admin
Setting | Value | |||
Suite Teardown | Delete table user | |||
Test Case | Action | Arguments | ||
[Setup] | Lancer appli en mode Admin | |||
Button Should Be Enabled | ${boutonNewUser} | |||
Ouvrir ecran creation utilisateur | ||||
Les champs sont obligatoires | ||||
Les champs sont au bon format | ||||
Creer un utilisateur | ||||
Creer Un Utilisateur en double | ||||
Keyword | Action | Arguments | ||
Start Application | org.lcx.myapp.MySampleApp | -admin | ||
Select Window | ${AppName} | |||
Push Button | ${boutonNewUser} | |||
Select Context | ${newUserPanel} | |||
${buttonTest} = | Get Button Text | ${boutonCreer} | ||
Should Be Equal | Create | ${buttonTest} | ||
Clear Text Field | ${login} | |||
Clear Text Field | ${password} | |||
Button Should Be Disabled | ${boutonCreer} | |||
Insert Into Text Field | ${login} | toto | ||
Insert Into Text Field | ${password} | tatata | ||
Button Should Be Enabled | ${boutonCreer} | |||
Clear Text Field | ${login} | |||
Clear Text Field | ${password} | |||
Button Should Be Disabled | ${boutonCreer} | |||
Clear Text Field | ${login} | |||
Clear Text Field | ${password} | |||
Insert Into Text Field | ${login} | 123 | ||
Button Should Be Disabled | ${boutonCreer} | |||
Insert Into Text Field | ${password} | 12345 | ||
Button Should Be Disabled | ${boutonCreer} | |||
Insert Into Text Field | ${login} | 1234 | ||
Button Should Be Disabled | ${boutonCreer} | |||
Insert Into Text Field | ${password} | 123456 | ||
Button Should Be Enabled | ${boutonCreer} | |||
Select Window | ${AppName} | |||
Insert Into Text Field | ${login} | John | ||
Insert Into Text Field | ${password} | 123456 | ||
Push Button | ${boutonCreer} | |||
Check Message | Utilisateur | Utilisateur cree avec succes | ||
Select Window | ${AppName} | |||
${newLoginValue} = | Get Text Field Value | ${login} | ||
Should Be Empty | ${newLoginValue} | |||
${newPasswordValue} = | Get Text Field Value | ${password} | ||
Should Be Empty | ${newPasswordValue} | |||
Connect To Database | com.mysql.jdbc.Driver | jdbc:mysql://localhost/mysampledb | mysampleuser | |
... | mysampleuser | |||
Check Content For Row Identified By Where Clause | login,password | John|123456 | user | |
... | login="John" | |||
Disconnect From Database | ||||
[Arguments] | ${title} | ${message} | ||
Select Dialog | ${title} | |||
${messageText} | Get Label Content | OptionPane.label | ||
Should Be Equal | ${message} | ${messageText} | ||
Close All Dialogs | ||||
Select Window | ${AppName} | |||
Insert Into Text Field | ${login} | Maurice | ||
Insert Into Text Field | ${password} | 123456 | ||
Push Button | ${boutonCreer} | |||
Check Message | Utilisateur | Utilisateur cree avec succes | ||
Select Window | ${AppName} | |||
Insert Into Text Field | ${login} | Maurice | ||
Insert Into Text Field | ${password} | 654321 | ||
Push Button | ${boutonCreer} | |||
Check Message | Avertissement | Un utilisateur avec cet identifiant existe deja | ||
Connect To Database | com.mysql.jdbc.Driver | jdbc:mysql://localhost/mysampledb | mysampleuser | |
... | mysampleuser | |||
Delete All Rows From Table | user | |||
Disconnect From Database | ||||
Tests développeurs
Comme vu dans la description des scénarios, il y a besoin pour compléter ces tests de niveau fonctionnel, d’ajouter des tests unitaires qui vont garantir la couverture de l’exigence.
Les tests unitaires étant du « code », il est plus facile d’obtenir des algorithmes de contrôle plus « couvrant ».
Test du contrôle de format des champs :
public void testFieldsFormat()throws Exception {
NewUserPanel panel = new NewUserPanel();
String text = "0123456789";
// only login
for (int i = 0; i < 10; i++) {
assertFalse(panel.controlFields(text.substring(0,i),""));
}
// only password
for (int i = 0; i < 10; i++) {
assertFalse(panel.controlFields("",text.substring(0,i)));
}
// both for password
for (int i = 0; i < 10; i++) {
if(i<6) {
assertFalse(panel.controlFields("1234",text.substring(0,i)));
} else {
assertTrue(panel.controlFields("1234",text.substring(0,i)));
}
}
// both for login
for (int i = 0; i < 10; i++) {
if(i<4) { assertFalse(panel.controlFields(text.substring(0,i), "123456"));
} else {
assertTrue(panel.controlFields(text.substring(0,i), "123456"));
}
}
}
Il faudrait ici compléter ce test par un test de robustesse.
C'est-à-dire un test non dérivé du besoin, mais répondant à une contrainte technique.
J’ai choisi des champs en base de données de type texte à 45 caractères max. Il faudrait donc écrire un test garantissant que ces limites sont correctement gérées.
Encore une fois, il me semble évident d’abord de demander l’avis du Product Owner sur ce point qui reste fonctionnel et compléter la user story par cette nouvelle exigence.
public void testCreateUser() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection
( "jdbc:mysql://localhost/mysampledb", "mysampleuser","mysampleuser");
String login = "cestuntest";
String password = "cestlepassword";
User user = new User(login, password);
String message = user.save(con);
assertEquals("Utilisateur cree avec succes", message);
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("select login, password, creation_date from user where login=\""+login+"\"");
rs.next();
assertEquals(login, rs.getString(1));
assertEquals(password, rs.getString(2));
assertNotNull(rs.getString(3));
// date non testée
user.delete(con);
ResultSet rs2 = s.executeQuery("select login, password, creation_date from user where login=\""+login+"\"");
assertFalse(rs2.next());
}
public void testCreateDoubleUser() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection
( "jdbc:mysql://localhost/mysampledb", "mysampleuser","mysampleuser");
String login = "cestuntest";
String password = "cestlepassword";
User user = new User(login, password);
user.save(con);
User user2 = new User(login, password);
try {
user2.save(con);
} catch (Exception e) {
assertEquals("Un utilisateur avec cet identifiant existe deja", e.getMessage());
} finally {
user.delete(con);
}
}
Pour des raisons pratiques et de temps, je n’ai ni géré les accents, ni les dates ! Mon Product Owner ne va pas être content.
Conclusion
La combinaison Tests fonctionnels/tests programmeurs (TDR et TDD) s’est avéré très efficace.
- D’un, je respecte le contrat défini par les critères d’acceptance,
- Deux, je communique avec le client sur la précision de son besoin,
- Trois, je garantie un jeu de test de non régression automatisé.
Cette approche m’a permis aussi de facilement définir quelle partie du code devait être testé unitairement. C’est une réponse au besoin exprimé par les tests fonctionnels qui m’a conduit à faire un test unitaire complémentaire garantissant une forme de robustesse et de cohérence. C’est le couplage de ces deux niveaux de test qui couvrent le besoin à 100%.
Je me suis aussi rendu compte que l’écriture des tests m’a obligé à codé d’une autre façon. Notamment, les méthodes de contrôles sont par besoin obligatoirement factorisées, mais aussi indépendantes du code appelant. Cela, me semble-t-il, améliore la maintenabilité du code.
Par exemple, le contrôle de format des champs m’a contraint à fournir une méthode prenant deux paramètres en entrée et renvoyant un booléen. Cette méthode à ensuite été connectée aux événements des deux champs login et password de l’IHM. Les tests IHMs assurant la bonne interaction entre la saisie utilisateur et le code de contrôle.
Annexe Tests unitaires
En attendant, petit exercice :
Reprenons le sujet de Jean-Michel Inglebert.
Peu importe la pratique, test first ou test « trop tard » !, l’objectif est de vérifier que la portion de code à tester rend le service demandé, le cas nominal, et répond aux appels extérieurs, cas dégradés et robustesse.
A ce niveau de test, ce sont les règles de gestion que l’on teste. La mécanique du système.
Prenons l’exemple du calcul de racine d’une équation à 2 inconnues.
ax² + bx + c = 0 avec a != 0
C’est une règle de gestion comme une autre.
On peut partir du test suivant simple, décrivant les trois cas majeurs :
- a = 1, b = 2, c = 3 è pas de résultat
- a = 1, b = 4, c = 4 è racine double X1=X2
- a = 1, b = 5, c = 3 è deux racines différentes X1 != X2
C’est pas mal, mais pas suffisant, comment généraliser :
Jean-Michel Inglebert propose par exemple de faire varier les paramètres:
- a = -1, b de 10 à 100000, c = 1
Il y erreur de calcul, le résultat n’est pas strictement égale à 0, dû aux erreurs de calcul sur virgule flottante. Il faut donc limiter le résultat. Dans ce cas, le résultat est ok à 10E-6 prés de zéro.
Le test couvre mieux la spec et précise la limitation de la méthode à 10E-6 pour b jusqu’à 10000
Et ne pas oublier de tester « avec a !=0 ». Mais quel comportement doit avoir l’appli dans ce cas ? Une exception, un code retour, un message à l’utilisateur… A demander à votre Product Owner.
Inscription à :
Articles (Atom)
