I. Le développement par les tests▲
Commençons cet article par la définition du TDD (source Wikipédia) : Le Test Driven Development (TDD) ou en français le développement piloté par les tests est une technique de développement de logiciel qui préconise d'écrire les tests unitaires avant d'écrire le code source d'un logiciel.
Contrairement à d'autres cycles de développement logiciel (modèle en cascade, cycle itératif) plaçant les tests en fin de cycle, le TDD place les tests en première place dans le cycle de développement. C'est une méthode dans laquelle l'écriture des tests automatisés dirige l'écriture du code source.
La mise en place de la méthode TDD offre de nombreux avantages au sein du développement d'un logiciel. Voici les avantages apportés par l'emploi de cette méthode.
Les tests unitaires sont réellement écrits
Comme évoqué dans les autres cycles de développement, les tests sont effectués à la fin d'un projet. D'après plusieurs enquêtes, 70 % des projets informatiques se terminent en retard, pour diminuer ce retard la phase de test est souvent réduite au strict minimum. Le fait de commencer par rédiger les tests permet de s'assurer que les tests seront écrits.
Fiabilité du code
En suivant la méthode traditionnelle, le développeur écrit une fonctionnalité et va ensuite procéder aux tests afin de s'assurer que ce qu'il a codé est valide. Mais inconsciemment le développeur teste son code comme il l'a conçu, le nombre de bogues trouvés est peu important et le risque de passer à côté de bogues est élevé.
Analyse détaillée et plus précise
En effet, lorsque le développeur écrit du code de test pour tester une implémentation qui n'existe pas encore, il va devoir penser aux détails de la méthode dont il a besoin pour écrire la spécification. Aussi, il va alors s'interroger sur le nom de la méthode, sa valeur de retour, ses paramètres, son comportement…, cela permet de clarifier la conception et d'écrire seulement du code utile.
Vérification démontrable, répétable et automatisée
Le fait de disposer d'un grand nombre de tests permet de s'assurer de la solidité et garantie du code.
Absence de régression
Lorsqu'un développeur modifie une méthode existante lors d'une phase de factorisation, il peut relancer les tests unitaires afin de s'assurer que sa modification n'a pas impacté l'existant et bénéficie d'un retour immédiat.
Couplage plus faible et conception simplifiée
Le TDD impose d'écrire le code des tests en peu de temps ce qui amène le développeur à concevoir du code avec un faible couplage et avec un niveau relativement simple.
Malgré les avantages listés ci-dessus, il faut bien comprendre que le TDD n'est pas une réponse miracle aux problèmes de développements dans les équipes, il n'empêche pas les bogues et les erreurs d'analyse, de conception… Quand on évoque le TDD, un inconvénient revient souvent :
Plus de travail
Depuis longtemps le développeur n'aime pas effectuer les tests, il trouve cette partie du travail fastidieuse, non valorisante et limite hors cadre de sa fonction. En écrivant des tests unitaires, le développeur a plus de travail.
II. Contexte technique▲
Windev : Version 20 et antérieure
Programmation : POO, test unitaire.
III. Les tests automatiques en WinDev▲
Pour lancer les tests automatiques dans WinDev, sélectionner l'option Enregistrer un nouveau test dans le menu Tests automatiques :
WinDev affiche une fenêtre proposant automatiquement de lancer votre projet et d'enregistrer les opérations graphiques. Cela permet de simuler des tests de vos interfaces. Dans le cadre de cet article, nous ne nous intéressons pas à cette fonctionnalité, cliquez sur le bouton annuler.
WinDev a créé un test vierge : Scénario1.
On ne va pas perdre les bons réflexes de programmation même pour des tests, on va donner un nom et une description à notre test. Effectuer un clic droit sur le test Scénario 1 et sélectionner l'option Description.
Modifier le nom et ajouter une description.
Maintenant, place à la programmation de nos tests, pour cela nous allons ajouter du code, clic droit, sélectionner l'option 'Code' :
Une fenêtre s'ouvre en style éditeur de code, la suite de ce tutoriel continuera au sein de cet éditeur.
IV. TDD▲
Dans le cadre de cet article, nous allons suivre étape par étape le développement d'une fonction calculant la date de fin d'un contrat de garantie.
- Nom de la procédure : proc_CalculDateFinContrat
- Paramètre 1 : date de début
- Paramètre 2 : durée de la garantie en mois
- Descriptif : ajoute de la durée à la date de début
Création du premier test : dans ce test, nous appelons la fonction et nous testons que la date n'est pas modifiée si la durée est à 0.
SI
proc_CalculDateFinContrat(
"19000101"
,
0
)
=
"19000101"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 01/01/1900 et 0 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 01/01/1900 et 0 mois"
)
FIN
Lançons maintenant le test, comme prévisible il échoue. Normal, nous n'avons pas créé la fonction gproc_CalculDateFinContrat.
Créons la procédure :
// Résumé : Calcule la date de fin de contrat
// Syntaxe :
//[ <Résultat> = ] gproc_CalculDateFinContrat (<pd_dateDebutContrat> est date, <pi_nbMois> est entier)
//
// Paramètres :
// pd_dateDebutContrat (date) : Date de début de contrat
// pi_nbMois (entier) : durée de contrat exprimée en mois
// Valeur de retour :
// Date : Date de fin de contrat
//
// Exemple :
// gproc_CalculDateFinContrat("20150401",12)
//
PROCEDURE
gproc_CalculDateFinContrat(
pd_dateDebutContrat est
une
Date
,
pi_nbMois est
un
entier
)
//----->Déclaration des variables
ls_dateTemp est
une
chaîne
ls_dateTemp=
DateVersChaîne(
Pd_dateDebutContrat,
"AAAAMMJJ"
)
RENVOYER
ChaîneVersDate(
ls_dateTemp,
"AAAAMMJJ"
)
Remarque : Depuis la version 20, WinDev permet de typer le paramètre de retour.
PROCEDURE
gproc_CalculDateFinContrat(
pd_dateDebutContrat est
une
Date
,
pi_nbMois est
un
entier
)
:
date
Relançons le test et constatons son succès :
Maintenant que nous avons créé le premier test et la procédure qui permet de vérifier le retour d'une date, nous allons coder l'incrémentation des mois et des années. Dans le code de notre test, nous ajoutons les trois cas suivants :
SI
gproc_CalculDateFinContrat(
"20150115"
,
1
)
=
"20150215"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 15/01/2015 et 1 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 15/01/2015 et 1 mois"
)
FIN
SI
gproc_CalculDateFinContrat(
"20150115"
,
12
)
=
"20160115"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 15/01/2015 et 12 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 15/01/2015 et 12 mois"
)
FIN
SI
gproc_CalculDateFinContrat(
"20150115"
,
18
)
=
"20160715"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 15/07/2015 et 18 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 15/01/2015 et 18 mois"
)
FIN
Lançons le test et constatons son échec :
Nous revenons dans le code de notre procédure pour développer l'incrémentation de la date, le développeur fait le choix de découper la date en entrée en trois variables :
- li_jour : type entier pour gérer les jours ;
- li_mois : type entier pour gérer les mois ;
- li_annee : type entier pour gérer les années.
Le code de la procédure avec la prise en charge de l'incrémentation,
PROCEDURE
gproc_CalculDateFinContrat(
pd_dateDebutContrat est
une
Date
,
pi_nbMois est
un
entier
)
//----->Déclaration des variables
ls_dateTemp est
une
chaîne
li_jour,
li_mois,
li_annee est
un
entier
ls_dateTemp=
DateVersChaîne(
pd_dateDebutContrat,
"AAAAMMJJ"
)
//----->Découpage de la date en 3 parties
li_jour=
Val
(
Droite
(
ls_dateTemp,
2
))
li_mois=
Val
(
Milieu
(
ls_dateTemp,
5
,
2
))
li_annee=
Val
(
Gauche
(
ls_dateTemp,
4
))
//----->Ajout des mois
li_mois+=
modulo
(
pi_nbMois,
12
)
li_annee+=
PartieEntière(
pi_nbMois/
12
)
//----->Concaténation des 3 parties
ls_dateTemp=
NumériqueVersChaîne
(
li_annee)+
NumériqueVersChaîne
(
li_mois,
"02D"
)+
NumériqueVersChaîne
(
li_jour,
"02D"
)
RENVOYER
ChaîneVersDate(
ls_dateTemp,
"AAAAMMJJ"
)
Relançons le test automatique et constatons son succès :
Le développement avance, on a l'incrémentation des mois et des années, maintenant place à la gestion des fins de mois.
Exemple : un contrat qui commence le 31/01/2015 pour une durée de trois mois se finira le 30/04/2015, même problème pour le mois de février, et attention aux années bissextiles. Ajoutons dans le code de notre test le code suivant :
SI
gproc_CalculDateFinContrat(
"20150131"
,
3
)
=
"20150430"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 31/01/2015 et 3 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 31/01/2015 et 3 mois"
)
FIN
SI
gproc_CalculDateFinContrat(
"20150131"
,
1
)
=
"20150228"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 31/01/2015 et 1 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 31/01/2015 et 1 mois"
)
FIN
SI
gproc_CalculDateFinContrat(
"20150131"
,
13
)
=
"20160229"
ALORS
TestEcritRésultat(
terInfo,
"Test OK de la date 31/01/2015 et 13 mois"
)
SINON
TestEcritRésultat(
terErreur,
"Erreur sur le test de la date 31/01/2015 et 13 mois"
)
FIN
Constatons l'échec des tests :
Revenons au code de notre procédure et mettons en place la gestion de fin de mois :
PROCEDURE
gproc_CalculDateFinContrat(
pd_dateDebutContrat est
une
Date
,
pi_nbMois est
un
entier
)
//----->Déclaration des variables
ls_dateTemp est
une
chaîne
li_jour,
li_mois,
li_annee est
un
entier
ls_dateTemp=
DateVersChaîne(
pd_dateDebutContrat,
"AAAAMMJJ"
)
//----->Découpage de la date en 3 parties
li_jour=
Val
(
Droite
(
ls_dateTemp,
2
))
li_mois=
Val
(
Milieu
(
ls_dateTemp,
5
,
2
))
li_annee=
Val
(
Gauche
(
ls_dateTemp,
4
))
//----->Ajout des mois
li_mois+=
modulo
(
pi_nbMois,
12
)
li_annee+=
PartieEntière(
pi_nbMois/
12
)
SI
li_jour=
31
_ET_ li_mois _DANS_(
4
,
6
,
9
,
11
)
ALORS
li_jour=
30
SINON
SI
li_jour>
28
ET
li_mois=
2
ALORS
SI
AnnéeBissextile(
li_annee)
ALORS
li_jour=
29
SINON
li_jour=
28
FIN
FIN
//----->Concaténation des 3 parties
ls_dateTemp=
NumériqueVersChaîne
(
li_annee)+
NumériqueVersChaîne
(
li_mois,
"02D"
)+
NumériqueVersChaîne
(
li_jour,
"02D"
)
RENVOYER
ChaîneVersDate(
ls_dateTemp,
"AAAAMMJJ"
)
Relançons les tests et constatons le bon déroulement :
Le développement par les tests prévoit une phase de factorisation. Après avoir parcouru la documentation, on prend note de l'existence du type date et surtout des possibilités d'effectuer des calculs sur les parties de celle-ci (incrémentation et décrémentation des jours, mois et années).
Voilà le code factorisé (dans ce cas on parle plus d'amélioration de code) :
PROCEDURE
gproc_CalculDateFinContrat(
p_dateDebutContrat est
une
Date
,
pi_nbMois est
un
entier
)
//----->Déclaration des variables
ld_dateTemp est
une
Date
ld_dateTemp=
pd_dateDebutContrat
ld_dateTemp..
Mois
+=
pi_nbMois
RENVOYER
ld_dateTemp
Nous avons modifié le code, nous devons nous assurer que celui-ci n'engendre pas de régressions. Pour cela, nous allons relancer nos tests et constater que ceux-ci fonctionnent.
Et voilà, nous venons de développer notre fonction en utilisant le TDD.
V. Conclusion▲
Dans cet article, nous avons décrit le cycle de développement par les tests en le mettant en application avec un exemple. La question que l'on peut se poser, est-ce que cette méthode est applicable pour l'ensemble des développements d'un projet ? Personnellement, je vois un intérêt à la mettre en place pour le développement :
- des fonctions métiers ;
- de la gestion de base de données ;
- des échanges de données avec des API, web services, Middleware.
Mais en ce qui concerne l'IHM, la validation par une personne physique reste la plus efficace. Effectivement, l'automatisation nous permet de valider le bon déroulement d'un traitement, mais il ne nous mettra pas en garde sur des problèmes d'utilisation :
- nombre de clics trop important ;
- texte pas intuitif ;
- les champs non alignés ;
- choix des couleurs ;
- …
Après la validation d'une personne, il est possible d'automatiser les tests des IHM pour vérifier
- la non-régression ;
- les erreurs de suppressions ou de changements de nom de champs.
Mais cette méthode ne rentre pas dans le cadre de la mise en place du TDD.
VI. Remerciements▲
Remerciements :
- relecture technique : LittleWhite;
- relecture orthographique : f-leb ;
- vérification avant mise en ligne : djibrildjibril.