Aller au menu - Aller au contenu

Icône Les tests unitaires

Mise à jour : 02/02/2012
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
22 955 visites depuis 7 jours, dont 247 sur ce chapitre classé 15/786
Une des grandes préoccupations des créateurs de logiciels est d’être certains que leur application informatique fonctionne et surtout qu’elle fonctionne dans toutes les situations possibles. Nous avons tous déjà vu notre système d’exploitation planter, ou bien notre logiciel de traitement de texte nous faire perdre les 50 pages de rapport que nous étions en train de taper. Ou encore, un élément inattendu dans un jeu où l'on arrive à passer à travers un mur alors qu’on ne devrait pas…

Bref, pour être sûr que son application fonctionne, il faut faire des tests.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire

Qu’est-ce qu’un test unitaire et pourquoi en faire ?

Un test constitue une façon de vérifier qu’un système informatique fonctionne.

Tester son application c’est bien. Il faut absolument le faire. C’est en général une pratique plutôt laissée de côté et rébarbative. Il y a plusieurs façons de faire des tests. Celle qui semble la plus naturelle est celle qui se fait manuellement. On lance son application, on clique partout, on regarde si elle fonctionne.
Celle que je vais présenter ici constitue une pratique automatisée visant à s’assurer que des bouts de code fonctionnent comme il faut et que tous les scénarios d’un développement sont couverts par un test. Lorsque les tests couvrent tous les scénarios d’un code, nous pouvons assurer que notre code fonctionne. De plus, cela permet de faire des opérations de maintenance sur le code tout en étant certain que ce code n’aura pas subi de régressions.
De la même façon, les tests sont un filet de sécurité lorsqu’on souhaite refactoriser son code ou l’optimiser.
Cela permet dans certains cas d’avoir un guide pendant le développement, notamment lorsqu’on pratique le TDD. Le Test Driven Development (TDD) (ou en Français développement piloté par les tests) est une méthode de développement de logiciel qui préconise d'écrire les tests unitaires avant d'écrire le code source d'un logiciel. Nous y reviendrons ultérieurement.

Un test est donc un bout de code qui permet de tester un autre code.


En général, un test se décompose en trois partie, suivant le schéma « AAA », qui correspond aux mots anglais « Arrange, Act, Assert », que l’on peut traduire en français par Arranger, Agir, Auditer.

  • Arranger : Il s’agit dans un premier temps de définir les objets, les variables nécessaires au bon fonctionnement de son test (initialiser les variables, initialiser les objets à passer en paramètres de la méthode à tester, etc.).
  • Agir : Ensuite, il s’agit d’exécuter l’action que l’on souhaite tester (en général, exécuter la méthode que l’on veut tester, etc.)
  • Auditer : Et enfin de vérifier que le résultat obtenu est conforme à nos attentes.

Notre premier test

Imaginons que nous voulions tester une méthode toute simple qui fait l’addition entre deux nombres, par exemple la méthode suivante :

Code : C#
1
2
3
4
public static int Addition(int a, int b)
{
    return a + b;
}


Faire un test consiste à écrire des bouts de code permettant de s’assurer que le code fonctionne. Cela peut-être par exemple :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void Main(string[] args)
{
    // arranger
    int a = 1;
    int b = 2;
    // agir
    int resultat = Addition(a, b);
    // auditer
    if (resultat != 3)
        Console.WriteLine("Le test a raté");
}


Ici, le test passe bien, ouf !
Pour être complet, le test doit couvrir un maximum de situations, il faut donc tester notre code avec d’autres valeurs, et ne pas oublier les valeurs limites :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
static void Main(string[] args)
{
    int a = 1;
    int b = 2;
    int resultat = Addition(a, b);
    if (resultat != 3)
        Console.WriteLine("Le test a raté");
    a = 0;
    b = 0;
    resultat = Addition(a, b);
    if (resultat != 0)
        Console.WriteLine("Le test a raté");
    a = -5;
    b = 5;
    resultat = Addition(a, b);
    if (resultat != 0)
        Console.WriteLine("Le test a raté");
}


Voilà pour le principe. Ici, nous considérons avoir écrit suffisamment de tests pour nous assurer que cette méthode est bien fonctionnelle.
Bien sûr, cette méthode était par définition fonctionnelle, mais il est important de prendre le réflexe de tester des fonctionnalités qui sont déterminantes pour notre application.

Voyons maintenant comment nous pourrions tester une méthode avec l’approche TDD.
Pour rappel, lors d’une approche TDD, le but est de pouvoir faire un développement à partir des cas de tests préalablement établis par la personne qui exprime le besoin ou suivant les spécifications fonctionnelles.

Imaginons que nous voulions tester une méthode qui calcule la factorielle d’un nombre. Nous savons que la factorielle de 0 vaut 1, la factorielle de 1 vaut 1. Commençons par écrire les tests :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static void Main(string[] args)
{
    int valeur = 0;
    int resultat = Factorielle(valeur);
    if (resultat != 1)
        Console.WriteLine("Le test a raté");

    valeur = 1;
    resultat = Factorielle(valeur);
    if (resultat != 1)
        Console.WriteLine("Le test a raté");
}


Le code ne compile pas ! Forcément, nous n’avons pas encore créé la méthode Factorielle. C’est la première étape. La suite de la méthode est de faire en sorte que le test compile, mais il échouera puisque la méthode n’est pas encore implémentée :

Code : C#
1
2
3
4
public static int Factorielle(int a)
{
    throw new NotImplementedException();
}


Il faudra ensuite écrire le code minimal qui servira à faire passer nos deux tests. Cela peut-être :

Code : C#
1
2
3
4
public static int Factorielle(int a)
{
    return 1;
}


Si nous exécutons nos tests, nous voyons que cette méthode est fonctionnelle car ils passent tous. La suite de la méthode consiste à refactoriser le code, à l’optimiser. Ici, il n’y a rien à faire tellement c’est simple.
On se rend compte par contre qu'on n'a pas couvert énormément de cas de tests, juste des tests avec 0 et 1 c'est un peu léger... Nous savons que la factorielle de 2 vaut 2, la factorielle de 3 vaut 6, la factorielle de 4 vaut 24, ... Continuons à écrire des tests. (Il faut bien sûr garder les anciens tests afin d’être sûr qu’on couvre un maximum de cas) :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void Main(string[] args)
{
    int valeur = 0;
    int resultat = Factorielle(valeur);
    if (resultat != 1)
        Console.WriteLine("Le test a raté");

    valeur = 1;
    resultat = Factorielle(valeur);
    if (resultat != 1)
        Console.WriteLine("Le test a raté");

    valeur = 2;
    resultat = Factorielle(valeur);
    if (resultat != 2)
        Console.WriteLine("Le test a raté");

    valeur = 3;
    resultat = Factorielle(valeur);
    if (resultat != 6)
        Console.WriteLine("Le test a raté");

    valeur = 4;
    resultat = Factorielle(valeur);
    if (resultat != 24)
        Console.WriteLine("Le test a raté");
}


Et nous pouvons écrire une méthode Factorielle qui fait passer ces tests :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static int Factorielle(int a)
{
    if (a == 2)
        return 2;
    if (a == 3)
        return 6;
    if (a == 4)
        return 24;
    return 1;
}


Lançons les tests, nous voyons que tout est OK. Cependant, nous n’allons pas faire des if en déclinant tous les cas possible, il faut donc repasser par l’étape d’amélioration et de refactorisation du code, afin d’éviter les redondances de code, d’améliorer les algorithmes, etc. Cette opération devient sans risque puisque le test est là pour nous assurer que la modification que l’on vient de faire est sans régression, si le test passe toujours bien sûr…
Nous voyons que nous pouvons améliorer le code en utilisant la vraie formule de la factorielle :

Code : C#
1
2
3
4
5
6
7
8
9
public static int Factorielle(int a)
{
    int total = 1;
    for (int i = 1 ; i <= a ; i ++)
    {
        total *= i;
    }
    return total;
}


Ce qui permet d’illustrer que par exemple la factorielle de 5 est égale à 1*2*3*4*5.
Relançons nos tests, ils passent tous. Parfait. Nous sommes donc certains que notre changement de code n’a pas altéré la fonctionnalité car les tests continuent de passer.
On peut même rajouter des tests pour le plaisir, comme la factorielle de 10, histoire d’avoir quelque chose d’un peu plus grand :

Code : C#
1
2
3
4
valeur = 10;
resultat = Factorielle(valeur);
if (resultat != 3628800)
    Console.WriteLine("Le test a raté");


Est-ce que cette méthode est optimisable ? Sûrement.
Est-ce qu’il y a un risque à optimiser cette méthode ? Aucun ! En effet, nos tests nous garantissent que s’ils continuent à passer, alors une optimisation n’entraine pas de régression dans la fonctionnalité.

On sait par exemple qu'il y a un autre moyen pour calculer une factorielle. Par exemple, pour calculer la factorielle de 5, il suffit de multiplier 5 par la factorielle de 4. Pour calculer la factorielle de 4, il faut multiplier 4 par la factorielle de 3, et ainsi de suite jusqu'à arriver à 1... Bref, pour obtenir une factorielle on peut se servir du résultat de la factorielle du nombre précédent. Ce qui peut s’écrire :

Code : C#
1
2
3
4
5
6
public static int Factorielle(int a)
{
    if (a <= 1) 
        return 1;
    return a * Factorielle(a - 1); 
}


Ici la méthode Factorielle est une méthode récursive, c’est-à-dire qu’elle s’appelle elle-même. Cela nous permet de d’indiquer que la factorielle d’un nombre correspond à ce nombre multiplié par la factorielle du nombre précédent. Bien sûr, il faut s’arrêter à un moment dans la récursion. On s’arrête ici quand on atteint le chiffre 1.
Pour s’assurer que cette factorielle fonctionne bien, il suffit de relancer les tests. Tout est OK, c’est parfait ! :)

Voilà donc un exemple de TDD. Bien sûr, la méthode est ici poussée au maximum pour que vous compreniez l’intérêt de cette pratique. On peut gagner du temps en partant directement sur la bonne implémentation. Mais vous verrez qu’il y a toujours des premiers essais qui satisfont les tests mais qu’il sera possible d’améliorer régulièrement notre code. Ceci devient possible grâce aux tests qui nous assurent que tout continue à bien fonctionner.
La pratique du TDD dépend de la façon dont le développeur appréhende sa philosophie de développement. Elle est présentée ici pour sensibiliser ce dernier à cette pratique mais son utilisation n’est pas du tout obligatoire.
Voilà pour les tests basiques. Cependant, utiliser une application console pour faire ses tests, ce n’est pas très pratique, vous en conviendrez. Nous avons besoin d’outils !

Le framework de test

Un framework de test est aux tests ce que l’IDE est au développement. Il fournit un environnement structuré permettant l’exécution de test et des méthodes pour aider au développement de ceux-ci.
Il existe plusieurs frameworks de test. Microsoft dispose de son framework, mstest, qui est disponible dans les versions payantes de Visual Studio. Son intérêt est qu’il est fortement intégré à l’IDE. Son défaut est qu’il ne fonctionne pas avec les versions gratuites de l’environnement de développement. Comme nous sommes partis dans ce tutoriel avec la version gratuite, Visual C# Express, nous n’allons pas pouvoir utiliser mstest.

Par contre, il existe d’autres framework de test, gratuits, comme le très connu NUnit. NUnit est la version .NET du framework XUnit, qui se décline pour plusieurs environnements, avec par exemple PHPUnit pour le langage PHP, JUnit, pour java, etc.

Première chose à faire, installer NUnit, pour cela, rendez-vous à cet emplacement pour le télécharger : http://www.NUnit.org/?p=download. La version que j’utilise dans ce tutoriel est la version 2.5.10.

Démarrez l’installation :

Image utilisateur


L’installation est en anglais, mais assez facile à suivre. Cliquez sur Next pour aller à l’écran suivant.
Après avoir accepté la licence, vous pouvez choisir l’installation classique :

Image utilisateur


À la fin de l’installation, nous pouvons voir que tout s’est bien passé :

Image utilisateur


Une fois le framework de test installé, nous pouvons créer un nouveau projet qui contiendra une fonctionnalité à tester. Je l’appelle MaBibliothequeATester. D’une manière générale, nous allons surtout tester des assemblys avec NUnit. Ici, je crée donc un projet de type bibliothèque de classes. Ce projet ne sera donc pas exécutable, car il ne s’agit pas d’une application console.
Et dedans, je vais pouvoir créer une classe utilitaire, disons Math, qui contiendra notre fameuse méthode de calcul de factoriel :

Code : C#
1
2
3
4
5
6
7
8
9
public static class Math
{
    public static int Factorielle(int a)
    {
        if (a <= 1)
            return 1;
        return a * Factorielle(a - 1);
    }
}


Puis ajoutons un nouveau projet de type bibliothèque de classes où nous allons mettre nos tests unitaires, appelons le MathTests.Unit. Ce n’est pas une norme absolue, mais je vous conseille de suffixer vos projets de test avec .Unit, ce qui permet de les identifier facilement.

Les tests doivent se mettre dans une classe spéciale. Ici aussi, pas de règle de nommage obligatoire, mais il est intéressant d’avoir une norme pour facilement s’y retrouver. Je vous propose de nommer les classes de tests en commençant par le nom de la classe que l’on doit tester, suivie du mot Tests. Ce qui donne : MathTests.
Pour être reconnue par le framework de test, la classe doit respecter un certain nombre de contrainte. Elle doit dans un premier temps être décorée de l’attribut [TestFixture]. Il s’agit d’un attribut qui permet à NUnit de reconnaitre les classes qui contiennent des tests.

Cet attribut étant dans une assembly de NUnit, vous devez rajouter une référence à l’assembly NUnit.framework :

Image utilisateur


et inclure l’espace de nom adéquat :

Code : C#
1
using NUnit.Framework;


Nous allons pouvoir créer des méthodes à l’intérieur de cette classe. De la même façon, une méthode pourra être reconnue comme une méthode de test si elle est décorée de l’attribut [Test].
Ici aussi, il est intéressant de suivre une règle de nommage afin de pouvoir identifier rapidement l’intention de la méthode de test. Je vous propose le nommage suivant :
MethodeTestee_EtatInitial_EtatAttendu()

Par exemple, une méthode de test permettant de tester la factorielle pourrait s’appeler :

Code : C#
1
2
3
4
5
6
7
8
9
[TestFixture]
public class MathTests
{
    [Test]
    public void Factorielle_AvecValeur3_Retourne6()
    {
        // test à faire
    }
}


Il existe plein d’autres attributs que vous découvrirez ultérieurement. Il est temps de passer à l’écriture du test et surtout à la vérification du résultat.
Pour cela, on utilise des méthodes de NUnit qui nous permette de vérifier par exemple qu’une valeur est égale à une autre attendue. Cela se fait grâce à la méthode Assert.AreEqual() :

Code : C#
1
2
3
4
5
6
7
[Test]
public void Factorielle_AvecValeur3_Retourne6()
{
    int valeur = 3;
    int resultat = MaBibliothequeATester.Math.Factorielle(valeur);
    Assert.AreEqual(6, resultat);
}


Elle permet de vérifier que la variable valeur vaut bien 6.
Rajoutons tant qu’on y est une méthode de test qui échoue :

Code : C#
1
2
3
4
5
6
7
[Test]
public void Factorielle_AvecValeur10_Retourne1()
{
    int valeur = 10;
    int resultat = MaBibliothequeATester.Math.Factorielle(valeur);
    Assert.AreEqual(1, resultat, "La valeur doit être égale à 1");
}


Il est important qu'une méthode de test ne s'occupe de tester qu'un seul cas d'une unique fonctionnalité, comme illustré juste au dessus. La première méthode teste la fonctionnalité Factorielle pour le cas où la valeur vaut 3 et la seconde s'occupe du cas où la valeur vaut 10. Vous pouvez rajouter autant de méthodes de tests que vous le souhaitez tant qu'elle sont décorée de l'attribut [Test].


J’en ai profité pour rajouter un message qui permettra d’indiquer des informations complémentaires si le test échoue.
Compilons maintenant notre projet et rendez-vous dans le répertoire d’installation de NUnit (par défaut : C:\Program Files\NUnit 2.5.10\bin\net-2.0) et lancez l’application NUnit.exe :

Image utilisateur


La première chose à faire est de créer un nouveau projet :

Image utilisateur


Appelez-le ProjetTest par exemple. Il faut ensuite ajouter une assembly de test, allez dans Project > Add Assembly :

Image utilisateur


Et allez pointer l’assembly de tests, à savoir MathTests.Unit.dll.
NUnit analyse l’assembly et fait apparaitre la liste des tests qui composent notre assembly (en se basant sur les attributs TestFixture et Test) :

Image utilisateur


Nous pouvons à présent lancer les tests en cliquant sur Run, et nous obtenons :

Image utilisateur


Ce qui nous permet de voir rapidement qu’il y a un test qui passe (icône verte) et un test qui échoue (icône rouge). Forcément, notre test n’était pas bon, il faut le réécrire. Nous voyons également qu’il nous indique que le résultat attendu était 1 alors que le résultat obtenu est de 3628800. Nous pouvons également voir le message que nous avons demandé d’afficher en cas d’erreur.
Le souci avec NUnit, c’est qu’à partir du moment où il a chargé la dll pour lancer les tests, il n’est plus possible de faire des modifications, car toute tentative de compilation provoquera une erreur où il sera mentionné qu’il ne peut pas faire de modifications car le fichier est déjà utilisé ailleurs. Ce qui est vrai. Ce qui va nous obliger à fermer NUnit et à le ré-ouvrir.
À noter que dans les versions payantes de Visual Studio, nous avons la possibilité de configurer NUnit en tant qu’outil externe, ce que nous ne pouvons pas faire avec la version gratuite. Il va falloir faire avec… Tristesse de la gratuité… :'(
Nous pouvons cependant un peu tricher en définissant un événement de post-compilation, qui consiste à lancer NUnit automatiquement. Pour cela, allez dans les propriétés du projet, onglet événement de builds et tapez la commande suivante :
"C:\Program Files\NUnit 2.5.10\bin\net-2.0\NUnit.exe" $(TargetFileName)

Image utilisateur


Ici, nous indiquons qu'après la compilation, il va lancer le programme NUnit.exe en prenant en paramètre le résultat de la compilation, représenté par la variable interne de Visual C# Express : $(TargetFileName).

Par contre, cela veut dire que NUnit va se lancer à chaque compilation, ce qui n’est peut-être pas le but recherché… Il faudra également fermer NUnit avant de pouvoir faire quoi que soit d’autre.
À noter que maintenant que nous savons faire de l’introspection sur les méthodes et les attributs d’une classe, nous devrions être capables de créer une petite application qui exécute les tests automatiquement…
Pour en finir avec NUnit, notons qu’il y a beaucoup de méthodes permettant de vérifier si un résultat est correct. Regardons les assertions suivantes :

Code : C#
1
2
3
4
5
6
bool b = true;
Assert.IsTrue(b);
string s = null;
Assert.IsNull(s);
int i = 10;
Assert.Greater(i, 6);


Elles parlent d’elles-mêmes. La première permet de vérifier qu’une condition est vraie. La deuxième permet de vérifier la nullité d’une variable. La dernière permet de vérifier que la variable est bien supérieure à une autre. À noter qu’elles ont chacune leur pendant (IsFalse, IsNotNull, Less). En regardant la complétion automatique, vous découvrirez d’autres méthodes de vérification, mais celles-ci sont globalement suffisantes.
Nous pouvons également utiliser une syntaxe un peu plus parlante comme :

Code : C#
1
Assert.That(i, Is.EqualTo(10));


Mais cette syntaxe est peut-être un peu plus parlante aux anglophones…
Il est également possible d’utiliser un attribut pour vérifier qu’une méthode lève bien une exception, par exemple :

Code : C#
1
2
3
4
5
6
[Test]
[ExpectedException(typeof(FormatException))]
public void ToInt32_AvecChaineNonNumerique_LeveUneException()
{
    Convert.ToInt32("abc");
}


Dans ce cas, le test passe si la méthode lève bien une FormatException.

Avant de terminer, présentons deux attributs supplémentaires : les attributs SetUp et TearDown.
Ils permettent de décorer des méthodes qui seront appelées respectivement avant chaque test et après chaque test. C'est l'endroit idéal pour factoriser des initialisations ou des nettoyages dont dépendent tous les tests.

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
[TestFixture]
public class MathTests
{
    [SetUp]
    public void InitialisationDesTests()
    {
        // rajouter les initialisations
    }

    [Test]
    public void Factorielle_AvecValeur3_Retourne6()
    {
        // test à faire
    }

    [TearDown]
    public void NettoyageDesTests()
    {
        // nettoyer les variables, ...
    }
}



Il existe plein d’autres choses utiles à dire sur NUnit, comme la description des autres attributs, ce que je ne vais pas faire ici. N’hésitez pas à aller voir sur internet des informations plus poussées pour approfondir votre maitrise des tests.

Le framework de simulacre

Un framework de simulacre fournit un moyen de tester une méthode en l’isolant du reste du système. Imaginons par exemple une méthode qui permet de récupérer la météo du jour, en allant la lire dans une base de données.
Nous avons ici un problème car lorsque nous exécutons le test le lundi, il pleut. Quand nous exécutons le test le mardi, il fait beau, etc.
Nous avons une information qui varie au cours du temps. Il est donc difficile de tester automatiquement que la méthode arrive bien à construire la météo du jour à partir de ces informations, vu qu’elles varient.
Le but de ces frameworks est de pouvoir bouchonner le code dont notre développement dépend afin de pouvoir le tester unitairement, sans dépendance et isolé du reste du système.
Cela veut dire que dans notre test, nous allons remplacer la lecture en base de données par une fausse méthode qui renvoie toujours qu’il fait beau. Cependant, ceci doit se faire sans modifier notre application, sinon cela n’a pas d’intérêt. Voilà à quoi servent ces framework de simulacres.
Il en existe plusieurs, plus ou moins complexe. Citons par exemple Moq (prononcez « moque-you ») ou encore Moles (il y en a plein d’autres).
L’intérêt de Moq est qu’il est simple d’accès, nous allons le présenter rapidement. Il permet de faire des choses simples et facilement. Tandis que Moles est un peu plus évolué mais plus complexe à prendre en main. Vous y reviendrez sans doute ultérieurement.
Pour le télécharger, rendez-vous sur : http://code.google.com/p/moq/downloads/list
Pas de système d’installation évolué, il y aura juste une dll à référencer. Ajoutez donc la référence à la dll moq.dll qui se trouve dans le sous-répertoire NET40.
Ensuite, pour pouvoir bouchonner facilement une classe, elle doit implémenter une interface. Imaginons la classe d’accès aux données suivante :

Code : C#
1
2
3
4
5
6
7
8
9
public class Dal : IDal
{
    public Meteo ObtenirLaMeteoDuJour()
    {
        // ici, c'est le code pour lire en base de données
        // mais finalement, peu importe ce qu'on y met vu qu'on va bouchonner la méthode
        throw new NotImplementedException();
    }
}


Qui implémente l’interface suivante :

Code : C#
1
2
3
4
public interface IDal
{
    Meteo ObtenirLaMeteoDuJour();
}


Avec l’objet Meteo suivant :

Code : C#
1
2
3
4
5
public class Meteo
{
    public double Temperature { get; set; }
    public Temps Temps { get; set; }
}


Et l’énumération Temps suivante :

Code : C#
1
2
3
4
5
public enum Temps
{
    Soleil,
    Pluie
}


Nous pourrons écrire un test qui bouchonne l’appel à la méthode ObtenirLaMeteoDuJour, qui doit normalement aller lire en base de données, pour renvoyer un objet à la place. Pour bien montrer ce fonctionnement, j’ai fait en sorte que la méthode lève une exception, comme ça, si on passe dedans ça sera tout de suite visible.
La méthode de test classique devrait être :

Code : C#
1
2
3
4
5
6
7
8
[Test]
public void ObtenirLaMeteoDuJour_AvecUnBouchon_RetourneSoleil()
{
    IDal dal = new Dal();
    Meteo meteoDuJour = dal.ObtenirLaMeteoDuJour();
    Assert.AreEqual(25, meteoDuJour.Temperature);
    Assert.AreEqual(Temps.Soleil, meteoDuJour.Temps);
}


Si nous exécutons le test, nous aurons une exception.
Utilisons maintenant Moq pour bouchonner cet appel et le remplacer par ce que l’on veut :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[Test]
public void ObtenirLaMeteoDuJour_AvecUnBouchon_RetourneSoleil()
{
    Meteo fausseMeteo = new Meteo { Temperature = 25, Temps = Temps.Soleil };
    Mock<IDal> mock = new Mock<IDal>();
    mock.Setup(dal => dal.ObtenirLaMeteoDuJour()).Returns(fausseMeteo);

    IDal fausseDal = mock.Object;
    Meteo meteoDuJour = fausseDal.ObtenirLaMeteoDuJour();
    Assert.AreEqual(25, meteoDuJour.Temperature);
    Assert.AreEqual(Temps.Soleil, meteoDuJour.Temps);
}


On utilise l’objet générique Mock pour créer un faux objet du type de notre interface. On utilise la méthode Setup à travers une expression lambda pour indiquer que la méthode ObtenirLaMeteoDuJour retournera en fait un faux objet météo. Cela se fait tout naturellement en utilisant la méthode Returns(). L’avantage de ces constructions est que la syntaxe claire parle d’elle-même à partir du moment où on connait les expressions lambdas.
On obtient ensuite une instance de notre objet grâce à la propriété Object et c’est ce faux objet que nous pourrons comparer à nos valeurs.
Bien sûr, ici, ce test n’a pas grand intérêt. Mais il faut le voir à un niveau plus général. Imaginons que nous ayons besoin de tester la fonctionnalité qui met en forme cet objet météo récupéré de la base de données ou bien l’algorithme qui nous permet de faire des statistiques sur ces données météos… Là, nous sommes sûrs de pouvoir nous baser sur une valeur connue de la dépendance à la base de données. Cela permettra également de décliner tous les cas possibles en changeant la valeur du bouchon et de faire les tests les plus exhaustifs possibles.
Nous pouvons faire la même chose avec les propriétés. Imaginons la classe suivante dont la propriété valeur retourne un nombre aléatoire :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IGenerateur
{
    int Valeur { get; }
}

public class Generateur : IGenerateur
{
    private Random r;
    public Generateur()
    {
        r = new Random();
    }

    public int Valeur
    {
        get
        {
            return r.Next(0, 100);
        }
    }
}


Nous pourrions avoir besoin de bouchonner cette propriété pour qu’elle renvoie un nombre connu à l’avance. Cela se fera de la même façon :

Code : C#
1
2
3
4
Mock<IGenerateur> mock = new Mock<IGenerateur>();
mock.Setup(generateur => generateur.Valeur).Returns(5);

Assert.AreEqual(5, mock.Object.Valeur);


Ici, la propriété Valeur renverra toujours 5 en se moquant bien du générateur de nombre aléatoire…
Je m’arrête là pour l’aperçu de ce framework de simulacre. Nous avons pu voir qu’il pouvait facilement bouchonner des dépendances nous permettant de faciliter la mise en place de nos tests unitaires. Rappelez-vous, pour qu’un test soit efficace, il doit pouvoir se concentrer sur un point précis du code sans être embêté par les dépendances éventuelles qui peuvent perturber l’état du test à un instant t.

En résumé


  • Les tests unitaires sont un moyen efficace de tester des bouts de code dans une application afin de garantir son bon fonctionnement.
  • Ils sont un filet de sécurité permettant de faire des opérations de maintenance, de refactoring ou d'optimisation sur le code.
  • Les frameworks de tests unitaires sont en général accompagnés d'outils permettant de superviser le bon déroulement des tests et la couverture de tests.
Chapitre précédent Sommaire

Partager

2 commentaires pour "Les tests unitaires"
Note moyenne : 3.05 / 4 (230 votes)
Pseudo Commentaire
Hors ligne bestsuan # Posté le 26/02/2012 à 10:10:30

Avis : Bon

Bonjour, Je voudrai bien savoir comment faire le deuxieme test , est ce qu on utulise la meme application que l application utuliser pour le 1er test (c-a-d Nunit)??
Merci pour la reponse.
Hors ligne nico.pyright # Posté le 27/02/2012 à 10:41:07
Groupe : Auteurs

Bonjour,

pouvez-vous me préciser de quels tests vous parlez ?

Dans tous les cas, il peut y avoir plusieurs tests dans la même classe. S'ils sont décorés de [TestMethod] alors ils pourront être lancés par NUnit.
 

Voir tous les commentaires