Coaching Agile

Agile, Scrum et autres pensées
Laurent Carbonnaux
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.

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à!

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

RobotFramework pour Eclipse 0.10 disponible

Nouvelle version disponible de Robotframework Eclipse Library, 0.10

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


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 :
  • 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
Voilà un premier « jet » de critères, mais comment évaluer leur précision, leur utilité.

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
Dans ce cas, le critère suivant doit être supprimé :
  • 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
Seconde analyse: Un critère d'acceptance doit être SMART

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.

Robotframework pour Eclipse: nouveau starter

Nouvelle version disponible de Robotframework Eclipse Library, 0.9

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.


image


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
Tester Écran Création Mode Non Admin
[Setup]
Lancer appli en mode non Admin



Button Should Not Be Visible
${boutonNewUser}







 
Keyword
Action
Arguments
Lancer Appli En Mode Non Admin
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
Créer Un Nouvel Utilisateur
[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
Lancer Appli En Mode Admin
Start Application
org.lcx.myapp.MySampleApp
-admin


Select Window
${AppName}







Ouvrir Ecran Creation Utilisateur
Push Button
${boutonNewUser}



Select Context
${newUserPanel}



${buttonTest} =
Get Button Text
${boutonCreer}


Should Be Equal
Create
${buttonTest}






Les Champs Sont Obligatoires
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}







Les Champs Sont Au Bon Format
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}







Creer Un Utilisateur
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








Check Message
[Arguments]
${title}
${message}


Select Dialog
${title}



${messageText}
Get Label Content
OptionPane.label


Should Be Equal
${message}
${messageText}


Close All Dialogs








Creer Un Utilisateur en double
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






Delete table user
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"));
}
}
}

Test de robustesse :
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.

Test de persistance nouvel utilisateur :
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());
}

Test création en double:
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.