| Les tests unitaires avec Nunit par Jean-Louis Vidal (jean-louis-vidal@wanadoo.fr) | ||
Êtes vous plutôt du style "organisé" avec une démarche itérative formalisée qui place les phases d'analyse et de conception avant le codage, le déboguage et les tests ou plutôt du style "sauvage" sans démarche dogmatique mais avec une propension à plonger sur le code à réaliser en comptant sur les tests pour vérifier le bon fonctionnement du code ?
Et bien … peu importe car nous retrouvons le mot TEST partout.
Le logiciel doit être l'expression "software" des besoins exprimés en amont. Ils sont une manière d'assurer la vérification du bon fonctionnement du logiciel en comparaison avec les résultats attendus. Mais où placer les tests ? Et bien sûrement pas au niveau du programme d'utilisation. En effet, dans ce cas chaque programme devrait coder les vérifications des règles qui sont souvent communes. Etant donné qu'en C# (ou en Java d'ailleurs) chaque classe peut posséder un point d'entrée unique, en l'occurrence le Main, on pourrait placer les tests unitaires à ce niveau. Si le regroupement est évident cela implique de polluer chaque classe par ses tests. Ne serait-il pas dans ce cas plus judicieux de sortir les tests des classes et les regrouper dans un espace de nommage (namespace) particulier ?
La technique classique : le code d'abord, les tests ensuite. La technique inversée : les tests d'abord et le code ensuite (et oui, n'avons-nous pas l'expression des besoins - voire les Use Cases et scénarios - pour nous guider dans leur écriture) La technique entrelacée... on s'aide de l'avancement du projet pour affiner et compléter les tests.
Le mieux est une procédure automatique. Les tests doivent pouvoir être exécutés sans intervention humaine et donner un résultat chiffré (par exemple 97 tests réussis sur 100). Voyons plus en détail le Framework de tests que nous vous proposons aujourd'hui : Nunit.
Nunit est un Framework open source de Tests Unitaires. Il correspond à la réécriture (ou le portage) du célèbre outil Java : JUnit. Le site de référence de NUnit est : http://www.nunit.org. Nous vous encourageons vivement à télécharger le fichier NUnit-V2.0.msi et à installer le produit. Mais attention ! Il convient de contrôler l'adhérence à une version spécifique du Framework .NET (par exemple, l'utilisateur de VisualStudio 2003 (V1.1), préfèrera peut-être recompiler NUnit à partir des sources (et du projet fourni avec NUnit-V2.0.msi).
Il existe énormément d'autres Frameworks existants que vous pourrez trouver à l'adresse suivante : http://www.sourceforge.net/projects, à laquelle il faudra rajouter le nom du projet, soit : JUnit (Java), NUnit (.NET), csUnit (c#), CppUnit (C++), CUnit (C), DUnit (Delphi), SUnit (Smalltalk), PyUnit (Python), PHPUnit (php), JSUnit( JavaScript), XMLUnit (XML DTD et XPath) et d'autres . . . PBUnit (PowerBuilder), SPUnit (SQL Stored Proc), HTTPUnit, DBUnit, JFCUnit, JUnitEE, TagUnit (Tags JSP) etc.
Attention au changement de Version, des modifications importantes ont été réalisées entre la V1 et la V2.
Style V1
La classe centrale est nommée TestCase et le développeur de test doit la dériver pour implémenter ses propres tests.
public abstract class
TestCase implements Test
{
private
final String fName;
public
TestCase(String name)
fName= name;
{ //le nom est utile pour savoir quel
test échoue }
public
abstract void
run(); //méthode abstraite pour obliger le développeur
à fournir une implémentation, il y mettra ses tests
…
}
Style V2 (plus simple : celui qui est présenté ici)
[TestFicture]
public class Test
{
[Test]
public
void MethodeTest(){. . .}//prototype
obligatoire public void <Methode>()
}
Sur un exemple simple, chaque règle de gestion peut (doit) être l'occasion d'un test, voire de plusieurs. Prenons le cas de l'objet suivant de type Voiture qui permet la construction, le démarrage/arrêt du moteur, l'accélération/freinage, ... Nous ferons en sorte de rendre la classe métier Voiture imparfaite de sorte à déclencher des erreurs de test.

namespace
Circulation {
public
class Voiture
{
private bool
moteurDemarre_ = false;
private int
vitesseMax_ = 0;
private int
vitesse_ = 0;
public Voiture(int
vitesseMax) {
vitesseMax_ = vitesseMax;
}
public void
DemarrerMoteur() {
moteurDemarre_ = true;
}
public void
ArreterMoteur() {
moteurDemarre_ = false;
}
public void
Accelerer(int dv) {
vitesse_ += dv;
}
public void
Ralentir(int dv) {
vitesse_ -= dv;
}
public void
FreinageArret() {
vitesse_ = 0;
}
public int
Vitesse {
get {
return vitesse_;
}
}
}
}
Que veut-on tester ?
Le scénario nominal : Création d'une voiture, démarrage, accélération en deçà de la vitesse max, vérification de la vitesse, freinage jusqu'à l'arrêt puis arrêt du moteur.
Un scénario " anormal " : Création d'une voiture, <oubli du démarrage>, accélération en deçà de la vitesse max, vérification de la vitesse, freinage jusqu'à l'arrêt puis arrêt du moteur, etc. Une première approche de test consiste donc à coder dans chacune des classes une méthode Main.
public
static
void
Main()
{
//scénario
Nominal
System.Console.Write("test
d’accélération n°1 : Accélération sous maximale : ");
Voiture
v1
=
new
Voiture(160);
v1.DemarrerMoteur();
v1.Accelerer(60);
if
(v1.Vitesse
!=
60)
System.Console.WriteLine("ERREUR");
//test
produit sur mesure
else
System.Console.WriteLine("OK");
//scénario
de l’accélération sans avoir démarré le moteur
System.Console.Write(
"test
d’accélération n°2 : Accélération moteur non démarré : ");
Voiture
v2
=
new
Voiture(160);
v2.Accelerer(60);
if
(v2.Vitesse
!=
0)
System.Console.WriteLine("ERREUR");
//test
produit sur mesure
else
System.Console.WriteLine("OK");
}
On peut compiler (en ligne de commande) avec la ligne suivante:
csc /out:Circulation.exe Voiture.cs
Et l'exécution nous donne :
C:\VC#JL\TestNUnit>circulation
test d'accélération n°1 : Accélération sous maximale : OK
test d'accélération n°2 : Accélération moteur non démarré : ERREUR
On peut également regrouper les tests dans une classe
spécialisée, ici TestCirculation qui pour l'instant ne teste que notre
Voiture.
L'exemple suivant résume la démarche :
namespace
Circulation {
public class
TestCirculation
{ //code
de test regroupé dans une classe "Test Métier"
public static void
Main()
{
//scénario Nominal
System.Console.Write("test
d’accélération n°1 : Accélération sous maximale : ");
Voiture v1 = new Voiture(160);
v1.DemarrerMoteur();
v1.Accelerer(60);
if (v1.Vitesse != 60)
System.Console.WriteLine("ERREUR");
//test produit sur mesure
else
System.Console.WriteLine("OK");
//scénario de l’accélération sans avoir démarré
le moteur
System.Console.Write(
"test d’accélération
n°2 : Accélération moteur non démarré : ");
Voiture v2 = new Voiture(160);
v2.Accelerer(60);
if (v2.Vitesse != 0)
System.Console.WriteLine("ERREUR");
//test produit sur mesure
else
System.Console.WriteLine("OK");
}
}
}
Dans ce cas, on compilera la classe Voiture sous la forme d'une DLL et le Test sous la forme d'un fichier d'extension .EXE. La ligne de commande suivante nous illustre le principe :
csc
/target:library /out:Circulation.dll Voiture.cs
csc /reference:Circulation.dll /out:TestCirculation.exe TestCirculation.cs
Nous venons donc de vous montrer ce que tout développeur/testeur ferait de manière classique. Le but de cet article étant de vous montrer l'utilisation du Framework de test Nunit, voyons comment cette démarche peut être automatisée. Pour ce faire, il nous faut transformer notre classe de test pour la rendre conforme à nunit :
using namespace
NUnit.Framework;
namespace Circulation
{
[TestFixture]
public class
TestCirculation
{
[Test]
public void
AccelerationSousMaximale()
//les méthodes peuvent avoir les noms désirés,
pas besoin de les préfixer par Test… utiliser les noms des use cases ou scénarios
est direct !
{ //scénario Nominal
Voiture v1 = new Voiture(160);
v1.DemarrerMoteur();
v1.Accelerer(60);
Assertion.AssertEquals(v1.Vitesse, 60);
}
[Test]
public void
AccelerationSansMoteur()
{
//scénario de l’accélération sans avoir démarré
le moteur
Voiture v2 = new Voiture(160);
v2.Accelerer(60);
Assertion.AssertEquals(v2.Vitesse, 0);
}
}
}
}
A noter qu'une surcharge de Assertion.Equals permet de placer sa propre chaîne de message en premier paramètre, pour un dépouillement des tests plus confortables. La compilation se fait avec (sur une seule ligne):
C:\VC#JL\TestNUnit>csc
/out:TestCirculation.dll /target:library /r:"c:\Program
Files\NUnit V2.0\bin\nunit.framework.dll",Circulation.dll TestCirculation.cs
Et le test sous l'environnement NUnit se lance par le menu "Démarrer\Programmes\NUnitV2.0\NUnit-GUI" ou par le raccourci placé sur le
bureau Windows, par l'installation automatique.

Le test possède à présent deux cas, dont un seul a réussi. Il a été placé dans une Assembly séparée (mais dans le même namespace) du code métier. Attention à bien prendre en compte le fait que chaque test doit absolument disposer d'une visibilité sur le namespace " métier ", alors que l'inverse est fortement déconseillé... On pourra donc éventuellement créer un namespace particulier réservé aux tests.
Faisons maintenant évoluer notre classe Voiture de façon à ce que les tests passent à 100%.
namespace
Circulation
{
public
class Voiture
{
private int
vitesseMax_ = 0;
private int
vitesse_ = 0;
private bool
moteurDemarre_ = false;
public Voiture(int
vitesseMax)
{
vitesseMax_ = vitesseMax;
}
public void
DemarrerMoteur()
{
moteurDemarre_ = true;
}
public void
ArreterMoteur()
{
moteurDemarre_ = false;
}
public void
Accelerer(int dv)
{
if (moteurDemarre_)
vitesse_ += dv;
}
public void
Ralentir(int dv)
{
vitesse_ -= dv;
}
public void
FreinageArret()
{
vitesse_ = 0;
}
public int
Vitesse
{
get
{
return vitesse_;
}
}
}
}
Utilisons l'option Reload du menu fichier de l'utilitaire NUnit-GUI et relançons ensuite les tests :

Parcourrons quelques unes des possibilités du Framework. Il est possible de vérifier certaines situations à l'aide des méthodes suivantes de la classe Assertion (la liste est obtenue par CTRL+Espace dans Visual Studio) :

La méthode Assertion.Assert est utilisée pour vérifier qu'une condition particulière (booléen) est vérifiée. Pour faire l'inverse nous utilisons donc Assertion.Fail.
Chaque méthode de test devant recourir à l'instanciation d'une Voiture, il est recommandé d'en faire un attribut de la classe mais attention, si nous déclarons et initialisons un simple attribut privé ainsi :
private Voiture v = new Voiture(160) ; Et que nous l'utilisons dans les diverses méthodes de test, alors les tests vont s'enchaîner sans réinitialisation de l'objet du test. En re-fabriquant le module TestCirculation.dll, et en re-testant le projet, tous les résultats deviennent faux ! Il est donc recommandé de procéder ainsi : Déclarer l'attribut dans la classe et placer dans la méthode d'initialisation un bloc [SetUp], le nom préféré pour cette méthode est Init.
[SetUp]
public void Init()
{
v = new
Voiture(160);
}
Que décidons nous de faire lorsque la méthode DemarrerMoteur est invoquée alors que le moteur est déjà démarré ? Cela doit être indiqué dans les spécifications de votre application. Disons (pour l'exemple) qu'une exception doit être lancée par la méthode DemarrerMoteur. Il y a moyen d'en exprimer la règle à l'aide du Framework. Revenons dans le fichier Voiture.cs pour définir notre Exception. Vu le bruit que cela fait de tourner la clef de contact alors que le moteur tourne, appelons la : CrouicException.
public
class CrouicException : System.ApplicationException { }//On rajoute alors le test suivant
[ExpectedException(typeof(CrouicException))]
[Test]
public void
DoubleDemarrageMoteur() {
//actionner la clef de contact après avoir déjà démarré
le moteur
v.DemarrerMoteur();
v.DemarrerMoteur();
//une
exception est attendue ICI
}
Comme l'exception n'est pas encore levée dans la méthode DemarrerMoteur, le cas de test échoue.

Nous revenons donc sur notre méthode pour la corriger :
public
void DemarrerMoteur(){Il est également envisageable de réaliser d'autres tests :
On notera que l'équipe chargée des tests n'est habituellement pas celle qui est chargée du développement. La technique de recherche et d'écriture de tests s'adapte parfaitement à ce cas, étant proche des besoins utilisateurs et ayant une connaissance minimale du fonctionnement interne des composants.

Quant aux attributs, ils sont de plusieurs natures :
Voici un exemple d'un test mis temporairement de côté, on ne peut pas l'oublier car la bande verte est transformée en jaune !

Remarque :
La bonne pratique est de ne placer qu'une cause d'erreur par méthode de test. En effet, le framework sort directement de la méthode après la première erreur détectée pour chaque méthode.
Maintenant comment faire tourner ces tests en ligne de commande ? Pour ce faire, recherchez le fichier nunit-console.exe ou compiler le projet (VSProj) correspondant dans le répertoire C:\Program Files\NUnit V2.0\bin
set path=%path%;”C:\Program Files\NUnit V2.0\bin”
Lancez ensuite les tests sur l'assembly choisie:
nunit-console /assembly:TestCirculation.dll
Le résultat est bien sûr envoyé à l'écran, mais EN PLUS dans un fichier TestResult.XML que l'on peut spécifier en ligne de commande et envoyé par défaut dans le répertoire de l'assembly testée.
nunit-console /assembly:TestCirculation.dll /XML:testCirculation.xml
Cette technique permet ensuite, par manipulation des fichiers XML, de consolider les différents tests, l'alimentation de bases de données, la source d'information pour les outils de suivi, etc.
Il existe également un Addin pour Visual Studio permettant d'utiliser simplement le framework NUnit à partir de l'environnement de développement ; Téléchargez le à partir du site http://sourceforge.net/projects/nunitaddin/
Pour l'installer, rien de plus simple, après avoir lancé l'exécutable, le programme enregistre un composant dans la base de registre. N'oubliez pas de vérifier son installation sous la clef HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.1\Addins\NUnitAddin.Connect\Options\NUnitAddin. Ensuite, lors du prochain démarrage de Visual Studio, vous verrez apparaître une nouvelle entrée sous le menu Tools/Addin Manager. On doit aussi retrouver un nouveau type de projet "Test Suite" dans chacun des langages (C#, VB, C++ et J#)
Enfin, il est possible de lancer le projet de test d'un simple clic droit (Run Test), comme le montre l'image suivante :

Nous espérons que cet article vous a convaincu de l'intérêt des tests unitaires. Non seulement ils sont nécessaires pour assurer la qualité d'une application mais le framework Nunit vous permet réellement de tirer parti de la puissance de .NET (attributs, …). Par ailleurs, les autres bénéfices apportés par Nunit sont nombreux :
N'hésitez pas à vous référer au site officiel où vous trouverez des modules spécialisés dans les tests sous ASP.NET, sous WebServices, ... bon tests !
Auteur : Jean Louis Vidal
Copyright © Avril 2003
|
Jean-Louis est Consultant et Formateur indépendant depuis 1998 après être passé par plusieurs (et parfois très longues) expériences dans de grosses sociétés. Diplômé de Supélec en 1980 il a très vite été attiré par le développement en architecture distribuée. Passion qu'il a aujourd'hui la chance de pouvoir communiquer à travers les formations qu'il anime (J2EE et .NET, MCT depuis 1998). Ses compétences ... |
Ressources