Souvenir d’eXtreme programming #5 : Les tests
Pour continuer notre feuilleton, nous allons nous intéresser au développement conduit par les tests (Test Driven Development).
Pour mettre en oeuvre le développement conduit par les tests, il convient d’utiliser un framework de test. Parmi les outils existants, le choix qui a été effectué il y plus de 2 ans était :
- CppUnit pour le code en C++. L’écriture des tests n’est pas aussi facile qu’en C#, mais l’intégration dans Visual Studio permet d’automatiser l’exécution des tests à l’issue d’une compilation.
- NUnit pour le code en C#. NUnit est plus qu’un portage de JUnit car il utiliser les attributs pour définir les classes et les fonctions de test. Depuis d’autre framework utilisant ce système ont été réalisé (notamment l’excellent MBUnit).
- NUnitAsp pour le test des pages ASP.Net. NUnitAsp est une extension de NUnit qui permet d’appeler des pages ASP, de les interpréter et de manipuler les objets d’interface constituant la page. Par rapport à HTTPUnit, NUnitAsp facilite l’interprétation de l’HTML généré. Par contre certains objets standard n’étant pas supporté par NUnitAsp à l’époque, il a fallut étendre NUnitAsp, c’est l’avantage de l’open-source !
Une fois les outils choisis, il faut former l’équipe au principe même du développement par les tests. Pour cela on procède en deux temps, avec d’abord une formation magistrale, puis du développement en binôme avec le coach (dans notre cas, c’est le CTO qui était aussi coach).
La formation
Pour effectuer une formation, il suffit d’un PC portable, d’un vidéo-projecteur et d’un mur blanc (ce fut le plus dur à trouver).
Tout d’abord on écrit un test pour une classe qui n’existe pas. Le test bien sur ne compile pas.
-
[TestFixture]
-
-
class MyClassOfTest
-
-
{
-
-
[Test]
-
-
MyTest
-
-
{
-
-
-
Assertion.Assert("Init Failed", MyClass.Init(10));
-
-
Assertion.AssertEqual("Balance is 10", 10, MyClass.Balance);
-
-
}
-
-
}
Puis on écrit le squelette de la classe et de ses méthodes.
-
class MyClass
-
-
{
-
-
private int balance;
-
-
bool Init(int initialBalance)
-
-
{
-
-
return false;
-
-
}
-
-
int Balance
-
-
{
-
-
get { return 0; }
-
-
}
-
-
}
Le test compile mais ne passe pas. Puis en écrit le code des méthodes et le test passe.
-
class MyClass
-
-
{
-
-
private int balance;
-
-
bool Init(int initialBalance)
-
-
{
-
-
balance = initialBalance;
-
-
return true;
-
-
}
-
-
int Balance
-
-
{
-
-
get { return balance; }
-
-
}
-
-
}
Le tout sur grand écran. Il faut voir les yeux des développeurs quand ils voie la barre verte des tests. Pour beaucoup c’est une révélation !
On n’a plus grand chose à montrer ensuite. En fait il faut aussi parler des bouchons (mock objets). Un bon exemple est l’envoi d’email qu’il faut mieux de pas effectuer pour de vrai (l’email toto@mail.com existe vraiment).
Coaching
Le principe est assez simple à comprendre à partir d’exemple. Pour faciliter l’apprentissage, le coach travaille en binôme avec chaque développeur pour montrer l’utilisation des tests lors de cas réels. Petit à petit le niveau des tests progressent, jusqu’à ce que cela deviennent un réflexe (”j’écris un test avant d’écrire du code”).
Ceci demande au coach beaucoup d’implication. Il faut d’un part aider à l’écriture des test, car dans certains cas ce n’est trivial. L’utilisation de mock objects permet souvent de contourner les difficultés.
De même la lourdeur d’utilisation de NUnitAsp oblige à séparer la logique applicative et l’accès aux données dans des librairies testables directement du code d’interface que l’on testera avec NUnitAsp.
Même si très vite tout le monde est convaincu par l’intérêt des tests, il faut régulièrement inciter la généralisation de cette pratique.
Pour cela il faut régulièrement évaluer le pourcentage de ligne de test par rapport aux ligne de code. Afin de faciliter cette mesure, on séparera systématiquement le code de production du code de test en deux projets jumeaux. Une répartition de 50 % doit être un objectif, souvent difficile à atteindre au début.
Et ça marche !
Un événement a définitivement convaincu tout le monde. Pour de multiples raisons, il a été décider de changer de système de base de donnée. Cela a été effectué par un binôme en quelques jours, sans aucun problèmes à l’issue de la mise en production. Heureusement les tests avaient permit de détecter plus de 10 régressions qui ont été réglés lors du développement.
A bientôt pour parler un peu plus du travail en binômes…
A suivre
avril 27th, 2005 à 23:48
Aller un petit commentaire sur l’ordonnancement.
Perso, c’est plutôt dans l’ordre suivant que je procède.
1 écriture du squelette des classes
2 écriture des classe de test
3 écriture du corps de methodes de la classe
Cela permet différente chose.
La première est que le squelette des classes n’est pas écrit à la main mais généré pour lors de la conception UML.
La seconde est que le squelette des classes de test est lui aussi généré.
Enfin, cela permet de compiler les tests même s’ils ne passent pas. On a ainssi la possibilité de les "tester" sur le plan syntaxique avant de passer à l’écriture des classes à proprement parler.
avril 28th, 2005 à 0:26
C’est effectivement une méthode utilisable dans un contexte plus “formel” (utilisation de conception UML) et “industriel” (génération de code) que celui dont je parle. L’avantage d’écrire les test (comme préconisé par certains créateurs de XP) c’est d’obliger à écrire les tests avant la structure des classes, ce qui d’une part oblige à n’écrire que du code qui sera validé par une test (mais c’est plutôt théorique) et d’autre part impose que le design détaillé soit créée au fur et à mesure de l’écriture des tests. A l’époque et dans ce contexte c’était une bonne façon de faire.
Avec les outils d’intégration continu et de génération de code qui existent maintenant ta méthode est sans doute plus adaptée.
J’en profite pour rappeler que ces souvenirs ne sont pas un guide, mais seulement le témoignage d’une mise en oeuvre qui n’a pas trop mal marché.