Accueil > .Net, Développement, Tests Unitaires > Les tests unitaires – Aperçu

Les tests unitaires – Aperçu

J’ai déjà parlé des tests unitaires dans un précédent billet.
Donc, dans la même lignée, je vais parler, ici, de la création de tests unitaires grâce à Visual Studio.

Ce billet est un aperçu des tests unitaires et permet juste de se lancer.
Du coup, je ne vais pas ici réaliser de scénarios de tests (puisque ce n’est pas le sujet).
Pour conséquence, les contenus des tests ne seront pas significatifs (ou pertinents).

 

Création du projet

 

Donc, après avoir créer la bibliothèque de classes, c’est la création du projet de tests :
Tests Unitaires - Création du projet


Dans l’explorateur de solutions, on peut voir plusieurs ajouts, dans le répertoire virtuel « Solution Items » :
– KR.Samples.vsmdi : c’est l’explorateur de tests, celui qui va les lister et où l’on pourra les organiser.
– Local.testsettings et TraceAndTestImpact.testsettings : des fichiers permettant la configuration des tests unitaires, notamment leur durée maximale (au niveau du test ou de la série).

Dans le projet de tests nouvellement créé, on pourra également noter la présence d’une classe « UnitTest1 » (à l’instar de « Class1 » lorsque l’on créé une bibliothèque de classes).

 

Fonctionnement d’un test

 

Commençons déjà par expliquer comment fonctionne un test avant d’en créer un.

Le projet de test va créer un répertoire « TestResults » à la racine de la solution.
Ce répertoire va contenir un fichier par série de tests exécutées (qu’il y ai un ou N tests à l’intérieur).

Le fichier sera nommé « [utilisateur]_[machine] [date : 2012-11-23] [heure : 14_46_32].trx »
Ce fichier est en réalité un XML (à l’instar des *.config) et va contenir les résultats des tests exécutés.
Voici un extrait :

<! -- [...] -->

  <ResultSummary outcome="Completed">
    <Counters total="5" executed="5" passed="5" error="0" failed="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
  </ResultSummary>
  <TestDefinitions>
    <UnitTest name="TestMethod2" storage="c:\projects\kr.samples\kr.sample.tests\bin\debug\kr.sample.tests.dll" id="573dfff6-1610-edea-dd1f-7abe12586777">
      <Execution id="fbef3ebc-0c83-4d1a-9183-dee0d01c0e0f" />
      <TestMethod codeBase="C:/Projects/KR.Samples/KR.Sample.Tests/bin/Debug/KR.Sample.Tests.DLL" adapterTypeName="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestAdapter, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" className="KR.Sample.Tests.ProcessHandlerTests, KR.Sample.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="TestMethod2" />
    </UnitTest>
  </TestDefinitions>

<! -- [...] -->

  <Results>
    <UnitTestResult executionId="c66263b5-c47e-483a-a816-c20745f5d00f" testId="afccc831-4468-f379-2a7e-951505b5282a" testName="TestMethod5" computerName="ComputerName" duration="00:00:05.0552120" startTime="2012-11-23T11:22:41.5459816+01:00" endTime="2012-11-23T11:22:46.6039816+01:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Failed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="c66263b5-c47e-483a-a816-c20745f5d00f">
      <Output>
        <ErrorInfo>
          <Message>La méthode de test KR.Sample.Tests.ProcessHandlerTests.TestMethod5 a levé une exception : 
System.Exception: Oups, ça a planté !</Message>
          <StackTrace>    à KR.Sample.Tests.ProcessHandlerTests.TestMethod5() dans C:\Projects\KR.Samples\KR.Sample.Tests\ProcessHandlerTests.cs:ligne 109
</StackTrace>
        </ErrorInfo>
      </Output>
    </UnitTestResult>
  </Results>

<! -- [...] -->

Comme on peut le voir, le résultat de chaque test est conservé.
Ce qui permet , entre autres, d’avoir le message d’une exception qui est levée ou même d’insérer des messages (j’y reviens plus tard).

A noter, tout de même, il n’y a pas de purge du répertoire.
Dans le cas où une série de tests est abandonnée (action utilisateur) en cours de route, il pourra également y avoir des répertoires « [utilisateur]_[machine] [date : 2012-11-23] [heure : 14_46_32] », qui sont utilisés par VS pour sa petite sauce interne.

 

Création d’un test unitaire

 

Pour ajouter un nouveau test unitaire, on va faire comme d’habitude : « Ajouter » > « Nouveau test… ».
Tests Unitaires - Création d'un test

La classe créée contient pas mal de choses, dés le départ.

Déjà, le TestContext, c’est une propriété qui permet d’accéder à plusieurs informations comme le TestName (nom de la méthode dans laquelle l’on se trouve), les répertoires de travail, etc.
Pour en savoir plus, je vous invite à aller la voir sur la MSDN : Class TestContext.

Il y a aussi des commentaires mettant en avant des attributs :

  • ClassInitialize : attribut devant décorer la méthode à exécuter avant tous les tests. Doit être présent une seule et unique fois.
  • ClassCleanup : attribut devant décorer la méthode à exécuter après tous les tests. Doit être présent une seule et unique fois.
  • TestInitialize : attribut devant décorer la méthode à exécuter avant chaque test. Doit être présent une seule et unique fois.
  • TestCleanup : attribut devant décorer la méthode à exécuter après chaque test. Doit être présent une seule et unique fois.

Et l’attribut TestMethod qui doit décorer chaque méthode de test.

Voici un exemple très basique :

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace KR.Sample.Tests
{
    [TestClass]
    public class DemoTests
    {
        private TestContext testContextInstance;

        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            // Se lance 1 seule fois au chargement de la classe, le contexte est positionné sur le premier test
            LoggerLight.Instance.Write("DemoTests.MyClassInitialize : {0}", testContext.TestName);
        }

        [ClassCleanup()]
        public static void MyClassCleanup()
        {
            // Se lance 1 seule fois au déchargement de la classe
            LoggerLight.Instance.Write("DemoTests.MyClassCleanup");
        }

        [TestInitialize()]
        public void MyTestInitialize()
        {
            // Se lance avant chaque test
            LoggerLight.Instance.Write("DemoTests.MyTestInitialize : {0}", TestContext.TestName);
        }

        [TestCleanup()]
        public void MyTestCleanup()
        {
            // Se lance après chaque test
            LoggerLight.Instance.Write("DemoTests.MyTestCleanUp : {0}", TestContext.TestName);
        }

        [TestMethod]
        public void TestMethod1()
        {
            LoggerLight.Instance.Write(" => Début du test DemoTests.{0}", TestContext.TestName);
            System.Threading.Thread.Sleep(5000);
            LoggerLight.Instance.Write(" => Fin du test DemoTests.{0}", TestContext.TestName);
        }

        [TestMethod]
        public void TestMethod2()
        {
            LoggerLight.Instance.Write(" => Début du test DemoTests.{0}", TestContext.TestName);
            System.Threading.Thread.Sleep(5000);
            LoggerLight.Instance.Write(" => Fin du test DemoTests.{0}", TestContext.TestName);
        }

        [TestMethod]
        public void TestMethod3()
        {
            LoggerLight.Instance.Write(" => Début du test DemoTests.{0}", TestContext.TestName);
            System.Threading.Thread.Sleep(5000);
            LoggerLight.Instance.Write(" => Fin du test DemoTests.{0}", TestContext.TestName);
        }

        [TestMethod]
        public void TestMethod4()
        {
            LoggerLight.Instance.Write(" => Début du test DemoTests.{0}", TestContext.TestName);
            System.Threading.Thread.Sleep(5000);
            LoggerLight.Instance.Write(" => Fin du test DemoTests.{0}", TestContext.TestName);
        }

        [TestMethod]
        public void TestMethod5()
        {
            LoggerLight.Instance.Write(" => Début du test DemoTests.{0}", TestContext.TestName);
            System.Threading.Thread.Sleep(5000);
            throw new Exception("Oups, ça a planté !");
        }
    }
}

LoggerLight est uniquement destiné à écrire dans un fichier texte.
C’est un singleton pour que le même fichier soit utilisé dans chaque série de tests (qu’il y ai 1 ou N classes de tests).

Voici l’output en utilisant deux classes identiques (excepté les libellés, DemoTests et Demo2Tests) :

23/11/2012 14:46:32 - DemoTests.MyClassInitialize : TestMethod3
23/11/2012 14:46:32 - DemoTests.MyTestInitialize : TestMethod3
23/11/2012 14:46:32 -  => Début du test DemoTests.TestMethod3
23/11/2012 14:46:37 -  => Fin du test DemoTests.TestMethod3
23/11/2012 14:46:37 - DemoTests.MyTestCleanUp : TestMethod3
23/11/2012 14:46:37 - DemoTests.MyTestInitialize : TestMethod2
23/11/2012 14:46:37 -  => Début du test DemoTests.TestMethod2
23/11/2012 14:46:42 -  => Fin du test DemoTests.TestMethod2
23/11/2012 14:46:42 - DemoTests.MyTestCleanUp : TestMethod2
23/11/2012 14:46:42 - Demo2Tests.MyClassInitialize : TestMethod2
23/11/2012 14:46:42 - Demo2Tests.MyTestInitialize : TestMethod2
23/11/2012 14:46:42 -  => Début du test Demo2Tests.TestMethod2
23/11/2012 14:46:47 -  => Fin du test Demo2Tests.TestMethod2
23/11/2012 14:46:47 - Demo2Tests.MyTestCleanUp : TestMethod2
23/11/2012 14:46:47 - Demo2Tests.MyTestInitialize : TestMethod5
23/11/2012 14:46:47 -  => Début du test Demo2Tests.TestMethod5
23/11/2012 14:46:52 - Demo2Tests.MyTestCleanUp : TestMethod5
23/11/2012 14:46:52 - DemoTests.MyTestInitialize : TestMethod4
23/11/2012 14:46:52 -  => Début du test DemoTests.TestMethod4
23/11/2012 14:46:57 -  => Fin du test DemoTests.TestMethod4
23/11/2012 14:46:57 - DemoTests.MyTestCleanUp : TestMethod4
23/11/2012 14:46:57 - DemoTests.MyTestInitialize : TestMethod1
23/11/2012 14:46:57 -  => Début du test DemoTests.TestMethod1
23/11/2012 14:47:02 -  => Fin du test DemoTests.TestMethod1
23/11/2012 14:47:02 - DemoTests.MyTestCleanUp : TestMethod1
23/11/2012 14:47:02 - Demo2Tests.MyTestInitialize : TestMethod3
23/11/2012 14:47:02 -  => Début du test Demo2Tests.TestMethod3
23/11/2012 14:47:07 -  => Fin du test Demo2Tests.TestMethod3
23/11/2012 14:47:07 - Demo2Tests.MyTestCleanUp : TestMethod3
23/11/2012 14:47:07 - DemoTests.MyTestInitialize : TestMethod5
23/11/2012 14:47:07 -  => Début du test DemoTests.TestMethod5
23/11/2012 14:47:12 - DemoTests.MyTestCleanUp : TestMethod5
23/11/2012 14:47:12 - Demo2Tests.MyTestInitialize : TestMethod1
23/11/2012 14:47:12 -  => Début du test Demo2Tests.TestMethod1
23/11/2012 14:47:17 -  => Fin du test Demo2Tests.TestMethod1
23/11/2012 14:47:17 - Demo2Tests.MyTestCleanUp : TestMethod1
23/11/2012 14:47:17 - Demo2Tests.MyTestInitialize : TestMethod4
23/11/2012 14:47:17 -  => Début du test Demo2Tests.TestMethod4
23/11/2012 14:47:22 -  => Fin du test Demo2Tests.TestMethod4
23/11/2012 14:47:22 - Demo2Tests.MyTestCleanUp : TestMethod4
23/11/2012 14:47:22 - DemoTests.MyClassCleanup
23/11/2012 14:47:22 - Demo2Tests.MyClassCleanup

Comme on peut le voir, il n’y a rien de vraiment séquentiel avec les méthodes d’une classe puis d’une autre.
C’est Visual Studio qui choisit dans quel ordre traiter les tests, ce qui peut occasionner de gros problèmes (exemple : quand les données de DemoTests doivent être nettoyées avant d’exécuter Demo2Tests).

Au sein du test, c’est du code, donc on peut faire un peu tout ce que l’on souhaite.

 

Exécuter une série de test

 

Rien de plus simple.
Il suffit d’ouvrir le fichier *.vsmdi ou d’afficher la barre d' »Outils de test » et de cliquer sur l’icône « Explorateur de tests » Tests Unitaires - Icône de l'explorateur de tests.

Là, on arrive sur un écran du style :
Tests Unitaires - Explorateur de tests

Il est possible de grouper les tests par le nom de la classe (le plus utile, à mon sens).
Il est aussi possible d’ajouter un nombre pas croyable de colonnes (type de test, propriétaire, priorité, heure de début ou de fin, etc.).
La colonne « Description » est alimentée par l’attribut du même nom que l’on peut positionner sur les méthodes de tests.
Pour la priorité, il existe un PriorityAttribute. Mais en l’état, il n’a pas l’air de faire grand chose ! Donc, pour ordonner les tests, on verra plus loin.

Donc, pour lancer une série de test, il suffit de cocher tout ou partie des tests, de faire un clic droit et de les exécuter ou de les lancer en debug. Et c’est aussi simple que ça.
On aura donc une nouvelle fenêtre pour afficher les résultats :
Tests Unitaires - Résultats

Cette fenêtre affiche ni plus ni moins que les résultats inscrits dans le fichier *.trx lié à la série de tests (voir plus haut).
En cas d’exception, il est possible d’avoir un détail avec le message de l’exception (ou de l’Assert, voir plus bas) :
Tests Unitaires - Exception

 

Ordonner les tests

 

Comme dit plus haut, les tests unitaires ne sont pas ordonnés par défaut.
Pour cela, il existe les « Test ordonné » (Ajouter -> Nouveau test… -> Test ordonné).
On arrive sur une fenêtre de ce type :
Tests Unitaires - Tests ordonnés

Tous les tests sont présents dans la partie gauche.
Il suffit de les sélectionner pour les mettre dans la partie droite (avec la flèche) et ensuite d’ordonner les tests dans la partie droite.
Cependant, le test ordonné fonctionne comme un seul et unique bloc autonome. Il ne sera donc pas possible d’ordonner X tests pour une série puis Y pour une autre, ce sera le test ordonné qui sera présent dans l’Explorateur de tests.

Dans la fenêtre de résultats, on aura donc une seule et unique ligne.
Cependant, il sera possible d’afficher le détail des résultats des tests (à partir du menu contextuel).
Tests Unitaires - Détail des résultats d'un test ordonné

Et en double cliquant sur la ligne en erreur, on a le détail de l’exception (même écran que vu plus tôt).

 

Assertions

 

Les assertions se basent sur la classe statique Assert.

Je ne vais pas détailler toutes les méthodes de la classe, parce que bon, les noms sont quand même assez parlant.
Mais il est très simple de l’utiliser, exemple (assez obvious, mais c’est pour la démo) :

[TestMethod]
public void TestMethod6()
{
    Int32 valeur1 = 42;
    Int32 valeur2 = 84;

    Assert.IsTrue(valeur1 == valeur2, "{0} et {1} ne sont pas égaux !", valeur1, valeur2);
}

Comme prévisible, ce cas va planter.
Dans le détail de l’exception, on va avoir le message correspondant au deuxième et troisième paramètres (comme avec un String.Format).

 

Conclusion

 

C’est déjà un long billet, donc je vais conclure là🙂

C’était donc un aperçu des tests unitaires avec Visual Studio.
Il est en effet d’aller beaucoup plus loin, par exemple en générant directement les méthodes (Ajouter -> Nouveau test… -> Assistant Test unitaire).

Quoiqu’il en soit, pour la qualité de l’applicatif, je le répète, il est très utile de réaliser des tests unitaires (pour peu qu’ils soient significatifs), mais si cela prend un peu plus de temps sur la phase de développement.

La solution utilisée pour ce billet est disponible (format .7z; VS 2010).

  1. Aucun commentaire pour l’instant.
  1. No trackbacks yet.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :