Intégration Continue avec Visual Studio et Team Foundation Server - Partie I, Présentation de MsTest et de Static Code Analysis

Pierre-Emmanuel Dautreppe

Nous allons présenter ici l'intégration continue dans le monde Microsoft.NET, c'est-à-dire avec Visual Studio.NET et le Team Foundation Server.
L'intégration continue s'articule autour de quatre outils :

- MsTest pour écrire des tests automatisés ;

- Static Code Analysis pour valider le code écrit selon un jeu de règles prédéfinies ;

- MsBuild pour automatiser un certain nombre de tâches (compilation, déploiement…) ;

- TFSBuild pour automatiser les tâches liées aux builds automatisés.

Cette première partie nous permettra non seulement de faire une introduction à l'intégration continue (pourquoi la mettre en place, ce qu'elle apporte, quels outils allons-nous utiliser…), mais surtout de décrire de façon très détaillée les deux premiers outils utilisés : MsTest et Static Code Analysis
La seconde partie de cet article se concentrera sur les deux autres outils.

- Titre : Intégration Continue avec Visual Studio et Team Foundation Server - Partie I, Présentation de MsTest et de Static Code Analysis

- Auteur : Pierre-Emmanuel Dautreppe

- Parution : 28 janvier 2009

- Mise à jour : 17 juin 2020

- Licence : Copyright ® 2009 Pierre-Emmanuel Dautreppe . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Contexte

- II. L'intégration continue - En quelques mots

- II-A. Pourquoi mettre en place de l'intégration continue ?

- II-A-1. J'ai déjà un serveur de source. N'est-ce pas suffisant ?

- II-A-2. Je veux des sources cohérentes

- II-B. Qu'est-ce que l'intégration continue ?

- II-B-1. Définition

- II-B-2. Choix de l'intervalle

- II-B-3. Politique de contrôle

- II-C. Que sont les BVT ?

- III. Présentation brève des outils de l'intégration continue

- III-A. MsTest

- III-B. Static Code Analysis

- III-C. MsBuild

- III-D. TFSBuild

- IV. Tests unitaires : Écrire des tests avec MsTest

- IV-A. Structure d'une classe de test

- IV-B. Vérifier la justesse d'un test

- IV-B-1. Vérifier la cohérence entre valeurs attendues et valeurs courantes

- IV-B-2. Vérifier qu'une exception a bien été lancée

- IV-B-3. Vérifier qu'une méthode s'exécute dans un temps raisonnable

- IV-C. Tester l'API non publique

- IV-C-1. Tester les membres internal

- IV-C-1-a. Présentation et utilisation

- IV-C-1-b. Utiliser l'attribut avec une DLL fortement signée

- IV-C-2. Tester les membres private et ou protected

- IV-C-2-a. Présentation

- IV-C-2-b. Création d'un accesseur via Visual Studio

- IV-C-2-c. Utilisation d'un accesseur

- IV-C-2-d. Création d'un accesseur dans le fichier de projet

- V. Tests unitaires : Exécuter des tests avec MsTest

- V-A. Exécution via Visual Studio

- V-B. Exécution en ligne de commande

- V-B-1. Exécution d'une liste de test

- V-B-2. Exécution de tous les tests d'une DLL

- V-B-3. Exécution de certains tests d'une DLL

- V-B-4. Configuration supplémentaire

- VI. Tests unitaires : Configuration des tests

- VI-A. Définition du nom des « tests runs »

- VI-B. Définition de la couverture de code

- VI-B-1. Paramétrage

- VI-B-2. Visualisation des résultats

- VI-B-3. Quel pourcentage rechercher ?

- VI-C. Déployer des fichiers

- VI-C-1. Paramétrage

- VI-C-2. Où les fichiers sont-ils déployés ?

- VI-C-3. Précaution d'emploi avec les versions plus anciennes de Visual Studio

- VI-D. TimeOut

- VII. Tests unitaires : Gestion des tests

- VII-A. Présentation de la « Test View »

- VII-B. Présentation de la « Test List Editor »

- VIII. Tests unitaires : Attributs additionnels

- VIII-A. Attributs de comportement

- VIII-A-1. Ignorer un (ou des) test(s)

- VIII-A-2. Déployer des fichiers

- VIII-B. Attributs informationnels

- VIII-B-1. Identifier le créateur du test

- VIII-B-2. Donner une priorité aux tests

- VIII-B-3. Ajouter des propriétés aux tests

- IX. Analyse Statique de Code : Présentation

- IX-A. Activer l'analyse statique de code

- IX-B. Lancer l'analyse statique de code

- IX-C. Explication du nommage des règles

- IX-D. Paramétrage des règles

- IX-D-1. Paramétrage via l'interface de Visual Studio

- IX-D-2. Paramétrage via fichier de projet

- X. Analyse Statique de Code : Corriger les erreurs

- X-A. Corriger une erreur

- X-B. Corriger plusieurs erreurs

- X-C. Regrouper les suppressions dans un même fichier

- X-C-1. Syntaxe du fichier de suppression global

- X-C-2. Préciser des méthodes avec noms réservés en IL

- X-C-3. Renommer le fichier de suppression global

- XI. Analyse Statique de Code : Limitations

- XII. Conclusion

- Remerciements

- Contact

- XIII. Annexes - Bibliographie

- XIII-A. Data-Driven Testing

- XIII-B. DeploymentItemAttribute

I - Contexte

Cela fait quatre ans que je mets en place de l'intégration continue sur mes différents projets, et ce exclusivement en utilisant les outils Microsoft.

J'ai donc voulu faire cet article pour m'adresse à la fois aux personnes connaissant déjà l'intégration continue, et aux novices.
C'est pourquoi nous commencerons par présenter ce qu'est l'intégration continue et les raisons poussant une équipe de développement à la mettre en place.
Mais l'objectif principal est de présenter les outils fournis par Microsoft aux développeurs pour mettre en place de l'intégration continue. Ces outils sont apparus sur le marché en 2005, principalement dans les versions « Team » de Visual Studio 2005, mais la plupart sont maintenant proposés dans les différentes versions de Visual Studio 2008.

C'est donc avec MsTest et Static Code Analysis que nous commencerons la présentation.

De nombreux liens vers la MSDN seront également donnés de façon à permettre au lecteur d'approfondir davantage les sujets traités. Notez que ces liens seront toujours donnés vers la MSDN online. Tous ces liens seront regroupés dans l'appendice (voir Bibliographie) et seront aussi donnés vers une installation locale de la MSDN (en supposant une utilisation de la MSDN fournie avec Visual Studio 2008 SP1).

II - L'intégration continue - En quelques mots

II-A - Pourquoi mettre en place de l'intégration continue ?

II-A-1 - J'ai déjà un serveur de source. N'est-ce pas suffisant ?

Tout le monde (ou presque) est convaincu de l'utilité de la mise en place d'un serveur de source (Source Control) sur un projet. En effet, il permet à la fois de garder un historique des différentes modifications sur les fichiers sources, mais en plus de faciliter le partage des sources entre plusieurs développeurs d'une même équipe.

Si ces fonctionnalités de base (1) sont probablement suffisantes pour beaucoup de développeurs, elles ne devraient pas être satisfaisantes pour un client, un chef de projet, un ingénieur qualité… En effet, quelle garantie ont-ils que ces fichiers sources stockés dans le Source Control sont valides et cohérents ?

II-A-2 - Je veux des sources cohérentes

Lorsqu'une équipe de quelques personnes travaille ensemble sur un même projet, les développeurs se retrouvent régulièrement à travailler ensemble sur les mêmes fichiers.

L'utilisation du Source Control « garantit » que les modifications de chaque développeur sont bien sauvegardées, à condition de procéder à des opérations de merge (c'est-à-dire des opérations de fusion) régulièrement entre les différentes versions des fichiers.

Cela ne veut malheureusement pas dire que les sources sont cohérentes : le travail en équipe peut régulièrement produire des sources ne compilant pas. Ce problème est le plus souvent causé par des conflits rencontrés lors de la fusion des sources - autrement dit deux développeurs ont travaillé sur les mêmes sources et il est difficile de savoir qui a raison -, mais pas uniquement.

Imaginons le cas de conflit suivant lorsque deux développeurs travaillent sur le même fichier :

Deux développeurs provoquent une erreur de compilation

Dans un tel processus, il est évident que Jean devra être très vigilant lors de son dernier « Commit ». En effet, il ne rencontrera aucune erreur ou difficulté lors de la fusion des sources, et pourtant, il risque de sauvegarder dans le système de gestion des sources un code ne compilant pas.

On peut même imaginer un cas beaucoup plus réaliste où :

- un projet contient des dizaines de fichiers ;

- chaque développeur peut travailler sur plusieurs fichiers en même temps ;

- certaines opérations de fusion présentent beaucoup de modifications et de conflits, et sont donc difficiles à réaliser ;

- certains fichiers ne peuvent pas être fusionnés comme des fichiers binaires ou des fichiers XML non formatés par exemple ;

- un projet comporte du code généré par certains outils de compilation, et ne doivent donc pas être fusionnés.

Bref, on voit aisément les risques qui peuvent exister de produire des sources ne compilant pas.

Et bien évidemment, même si les sources compilent, ceci ne garantit en rien qu'elles soient correctes vis-à-vis des attentes du client. Et c'est cette cohérence qui nous intéresse par-dessus tout. Et c'est là que l'intégration continue entre en jeu.

II-B - Qu'est-ce que l'intégration continue ?

II-B-1 - Définition

Comme son nom l'indique, « l'intégration continue » permet d'intégrer en permanence (de façon rapide et « continue ») les sources des différents développeurs afin de valider leur intégrité et leur conformité par rapport aux demandes clients.

Cette validation implique donc d'avoir un processus qui va à intervalle régulier :

- récupérer les sources des tous les développeurs ;

- compiler ces sources ;

- valider éventuellement la conformité de ces sources via par exemple :

- exécution d'une analyse de code,

- exécution de tests de validation ;

- déployer les sources pour que le produit puisse être soit téléchargé, soit testé par une équipe dédiée.

Ce processus d'intégration continue est appelé « build ».

II-B-2 - Choix de l'intervalle

Ce build sera donc lancé à intervalle régulier, et ce, automatiquement. Cet intervalle sera choisi selon les besoins du projet. Typiquement, un build sera programmé toutes les nuits (2) , toutes les heures ou à chaque remontée de code dans le système de gestion des sources.

Typiquement le choix de cet intervalle est fait selon la criticité du projet ou selon la criticité d'un moment particulier de la vie d'un projet. L'intégration sera typiquement faite beaucoup plus souvent à l'approche d'une livraison que lors d'une journée de développement normal.

II-B-3 - Politique de contrôle

Cette intégration continue peut s'accompagner d'une politique de contrôle des sources plus ou moins fortes afin de réduire les check-in douteux. On peut citer quelques exemples d'obligations :

- commenter le check-in (explication des modifications réalisées et de la motivation de ces modifications : demande client, correction de bug…)

- une ou plusieurs personnes doivent revoir le code, typiquement ;

- revue de la qualité du code ;

- revue des performances ;

- revue de la sécurité ;

- les tests doivent être lancés, et doivent tous passer ;

- une analyse statique du code (3) doit être lancée et toutes les erreurs / avertissements doivent être corrigées.

Notez que Visual Studio propose par défaut les trois dernières règles décrites ci-dessus. Il propose de plus un modèle objet ouvert et simple afin que tout développeur puisse développer ses propres règles de validation. (4) La création de telles règles est plus complexe et ne sera pas détaillée ci-après.

II-C - Que sont les BVT ?

Nous disions lors de la présentation de l'intégration continue qu'il était important de pouvoir valider la conformité des sources. Dans le monde Microsoft, on parle de BVT, ou de Build Validation Tests.

Visual Studio nous propose plusieurs types de tests possibles, mais qui ne seront pas disponibles dans toutes les versions.

Pour rappel, Visual Studio est disponible dans les versions suivantes :

- Version Express (gratuites - plusieurs versions disponibles selon les besoins) ;

- Visual Studio Professional ;

- Visual Studio Team Edition For Software Developpers ;

- Visual Studio Team Edition For Software Architects ;

- Visual Studio Team Edition For Software Testers ;

- Visual Studio Team Edition For Database Professionals (5) ;

- Visual Studio Team Suite (6).

Dans Visual Studio 2005 et 2008, quatre types de tests sont proposés. Le tableau ci-dessous présente la disponibilité de ces types de tests selon les versions de Visual Studio.

Différentes versions de Visual Studio - Différents outils de test

Type de test

Versions de Visual Studio

Commentaires

Pro.

Team Edition For Software

Team Suite

Dev.

Test.

Unit Tests

Tests unitaires, similaires à ceux produits par les outils xUnit (NUnit, JUnit…)

Web Tests

Successeur de Microsoft ACT : permet d'enregistrer des tests à partir d'une application web

Load Tests

Permet de créer des tests de montée en charge en simulant la présence de plusieurs clients

Ordered Tests

Liste ordonnée de tests permettant de lancer les tests dans un ordre bien déterminé

Les autres versions de Visual Studio - non reprises dans ce tableau - ne nous offrent pas la possibilité d'écrire des tests. Notez cependant que les tests unitaires ne sont disponibles dans la version « Professional » que depuis Visual Studio 2008. La version 2005 les avait réservés aux versions « Team ».

Notez que Visual Studio 2010 (version « For Testers ») proposera aussi d'autres types de tests. On peut citer la possibilité d'enregistrer des tests sur une application Winform ou WPF, à l'instar des WebTest pour une application Web. On pourra alors demander de générer une classe C# contenant le code C# de ce test, qui pourra alors être exécuté et réexécuté comme tout autre test.

Seuls les « Unit Tests » seront décrits dans ce document.

III - Présentation brève des outils de l'intégration continue

Le monde Microsoft (.NET) met quatre outils à notre disposition afin de mettre en place de l'intégration continue sur nos projets. Tous ces outils, bien que pouvant être utilisés indépendamment, s'articulent autour du Team Foundation Server de façon à en tirer le meilleur parti.

III-A - MsTest

L'écriture de tests unitaires est la première étape de l'intégration continue pour garantir l'exécution et la conformité du code vis-à-vis des attentes du client.

De nombreux langages comprennent des frameworks de tests unitaires type xUnit qui ont tous un cœur semblable si ce n'est commun. On peut citer par exemple : JUnit, CppUnit, VbUnit, NUnit, NUnitHttp, .

Tout outil indispensable de la communauté .NET est « destiné » - à plus ou moins court terme - à être intégré à Visual Studio et NUnit n'a pas échappé à la règle. On retrouve en effet depuis Visual Studio 2005 (namespace Microsoft.VisualStudio.QualityTools) un framework très similaire à celui que propose NUnit pour l'écriture de tests unitaires. C'est l'utilitaire MsTest qui permet l'exécution de ces tests. (7)

Il est bien sûr tout à fait possible de continuer à utiliser NUnit pour l'écriture de tests unitaires, mais cela requerra un peu plus de travail si nous voulons automatiser ces tests dans notre build.

Notez que cet outil est installé par défaut dans le répertoire « %ProgramFiles%\Microsoft Visual Studio 8\Common7\IDE » pour Visual Studio 2005 et « %ProgramFiles%\Microsoft Visual Studio 9.0\Common7\IDE » pour Visual Studio 2008.

III-B - Static Code Analysis

Dès la version 1.0 du framework .NET, la communauté Microsoft a mis à disposition des développeurs l'outil FxCop. Il est maintenant intégré dans Visual Studio 2005.

Cet outil (renommé « Static Code Analysis ») permet de faire une analyse statique du code autrement dit une analyse lors de la compilation et non lors de l'exécution.

Il permet de vérifier qu'un certain nombre de règles (paramétrables) ont bien été respectées. On peut citer - entre autres - des règles liées au respect :

- des conventions de nommage (une classe héritant de System.Exception doit être suffixée par Exception) ;

- des principes de design (un événement doit avoir une signature acceptant deux paramètres : un System.Object et une classe héritant de System.EventArgs) ;

- des performances (utiliser const plutôt que shared readonly) ;

- de sécurité (un constructeur statique doit être privé).

Vous pouvez soit lancer une analyse à la demande (clic droit sur un projet et « Run Code Analysis »), soit la lancer à chaque compilation en activant l'analyse statique dans les propriétés du projet (Clic droit sur un projet et Properties / Code Analysis / Enable Code Analysis).

III-C - MsBuild

MsBuild est la pierre angulaire de l'intégration continue dans le monde Microsoft : c'est l'outil qui permet l'automatisation de tâches diverses (compilation, déploiement d'une solution, exécution de tests…).

La communauté Microsoft avait déjà fourni des outils d'intégration continue - pour la plupart des portages du monde Java - comme CruiseControl.NET et NAnt. Mais c'est à partir de la version 2005 que Microsoft inclut son propre moteur au sein de Visual Studio.

Il est même utilisé en interne pour la compilation des solutions et des projets. Il est donc beaucoup plus « simple » (ou tout du moins envisageable sans refaire tout le processus en ligne de commande) de paramétrer la compilation de nos projets dans Visual Studio en modifiant le fichier de build utilisé par Visual Studio. (8)

MsBuild permet en fait d'exécuter un fichier de projet (ou fichier de build), traditionnellement d'extension « .proj » ou « .target ». (9) Il s'agit en fait d'un fichier XML représentant une série d'actions.

Ce fichier de projet génèrera une fois exécuté un fichier texte comportant tous les messages, avertissements ou erreurs relatifs à l'exécution.

L'outil MsBuild est installé avec le framework, autrement dit dans le répertoire « %Windir%\Microsoft.NET\Framework\VersionFramework », soit typiquement dans les répertoires :

- « %Windir%\Microsoft.NET\Framework\v2.0.50727 » pour .NET 2.0 ;

- « %Windir%\Microsoft.NET\Framework\v3.5 » pour .NET 3.5.

III-D - TFSBuild

TFSBuild est une surcouche au-dessus de MsBuild pour réaliser plus facilement de l'intégration continue avec le Team Foundation Server, en permettant de simplifier la création de builds. Il va typiquement exécuter des actions comme :

- récupération des sources à partir du Source Control ;

- compilation des sources ;

- exécution des tests ;

- publication des résultats sur le Team Foundation Server.

Le build sera alors typiquement considéré comme réussi si :

- la compilation des sources a réussi ;

- tous les BVTs sont passés avec succès.

Le processus de build est cependant entièrement paramétrable et il peut donc échouer pour bien d'autres raisons. Nous verrons dans une autre partie comment le paramétrer.

Informations générales sur le build

Étapes du build : Get, compilation, exécution des tests

Liste des tests exécutés et résultats

Liste des derniers changesets (10)

IV - Tests unitaires : Écrire des tests avec MsTest

Cette partie n'abordera que certaines des possibilités offertes par les tests unitaires. Entre autres, le « Data-Driven Testing » ne sera absolument pas abordé. Vous pourrez trouver plus de détails sur ce sujet dans la MSDN (voir la bibliographie « Data-Driven Testing »).

IV-A - Structure d'une classe de test

MsTest ainsi que les dernières versions de NUnit se reposent sur des attributs spécifiques pour définir les différentes méthodes de test à exécuter.

Il est également possible de définir des méthodes de pré et postprocessing des tests. Vous trouverez ci-dessous un exemple de classe définissant toutes les méthodes possibles, ainsi que l'ordre dans lequel celles-ci seront exécutées.

Liste des différentes méthodes pouvant être exécutées avant ou après un test

[TestClass]
public class MyClassTest
{
[AssemblyInitialize]
public static void MyAssemblyInitialize(TestContext context)
{
//1. Sera lancé une et une seule fois avant que le moindre test
//   de l'assembly ne soit exécuté
}

[ClassInitialize]
public static void MyClassInitialize(TestContext testContext)
{
//2. Sera lancé une et une seule fois avant que le moindre test
//   de la classe de test ne soit exécutée
}

[TestInitialize]
public void MyTestInitialize()
{
//3. Sera lancé avant chacun des tests de la classe de test
//   Cette méthode est donc commune à tous les tests de la classe
}

[TestMethod]
public void MyTest()
{
//4. Test à exécuter - Ne sera lancé qu'une seule fois
}

[TestCleanup]
public void MyTestCleanup()
{
//5. Sera lancé après chacun des tests de la classe de test
//   Cette méthode est donc commune à tous les tests de la classe
}

[ClassCleanup]
public static void MyClassCleanup()
{
//6. Sera lancé une et une seule fois après que tous les tests de
//   la classe de test auront été exécutés
}

[AssemblyCleanup]
public static void MyAssemblyCleanup()
{
//7. Sera lancé une et une seule fois après que tous les tests de
//   l'assembly auront été exécutés
}
}

Notez que seuls les attributs TestClass et TestMethod sont obligatoires. Les autres fonctions et attributs décrits ci-dessus permettent de contrôler l'initialisation et le nettoyage qui peuvent être nécessaires pour chacun des tests, mais sont tout à fait optionnels.

IV-B - Vérifier la justesse d'un test

Un test doit vérifier le bon fonctionnement d'une méthode. Pour cela, il peut vérifier que :

- la valeur du paramètre de retour est correcte :

- la valeur des paramètres de type out ou ref est correcte :

- l'exception attendue a bien été levée :

- la réponse a bien été obtenue en moins de X secondes :

- la fonction a bien effectué le travail souhaité (en base de données, dans un fichier…).

Notez que pour être certain du résultat d'une méthode, on doit être en environnement contrôlé. Si par exemple une méthode va dépendre d'une valeur en bases de données (11) , cette valeur doit être connue et donc être insérée par le test lui-même.

Autrement dit, on a trois éléments différents de validation étant, du plus courant au moins courant :

- cohérence entre valeurs attendues et valeurs courantes (que ces valeurs courantes aient été retournées par la méthode ou aient été lues dans la base de données, dans un fichier…) ;

- présence d'une exception ;

- rapidité d'exécution (performance).

IV-B-1 - Vérifier la cohérence entre valeurs attendues et valeurs courantes

Pour vérifier la cohérence des valeurs, nous avons à notre disposition des classes d'assertion : il s'agit des classes Assert, StringAssert et CollectionAssert.

On ne détaillera pas ici toutes leurs méthodes, mais elles ont toutes des caractéristiques communes :

- ce sont toutes des méthodes statiques (ces classes sont d'ailleurs statiques) ;

- elles ont toutes au moins trois surcharges :

- la première prenant un message (message qui sera affiché si l'assertion - le test – échoue),

- la seconde prenant un message (avec formatage) et un « params de string » comprenant les paramètres. Il n'est donc pas nécessaire d'utiliser un string.Format avec les méthodes d'assertion,

- une autre surcharge existe sans aucun message d'erreur,

- les autres paramètres (et donc surcharges éventuelles associées) dépendent de chacune des méthodes ;

- elles ne retournent aucun paramètre ;

- en cas d'échec, elles font stopper immédiatement le test. Il faut savoir qu'en interne quand une assertion échoue, c'est une exception de type AssertFailedException qui est levée. (12) Il est donc possible (mais non souhaitable) d'encapsuler un test avec un bloc « try / catch » afin de cumuler plusieurs erreurs. Attention dans ce cas si aucun Assert.Fail n'est effectué, le test sera considéré comme réussi.

Liste des principales méthodes d'assertion

Classe

Quelques Méthodes

Commentaire

Assert

AreEqual
AreNotEqual

Compare l'égalité de 2 valeurs
Existe en version générique et non générique
Possibilité de passer un « delta » : différence maximale autorisée

Fail

Force l'échec d'un test

Inconclusive

Statut intermédiaire entre l'échec et le succès : lorsque le test a été exécuté, on ne pouvait conclure quant à sa réussite ou non

IsTrue
IsFalse

Teste une condition ou une valeur booléenne

IsNull
IsNotNull
IsInstanceOf
IsNotInstanceOf

Teste un objet selon sa nullité ou son type

StringAssert

Contains
StartsWith
EndsWith

Teste la présence d'une chaîne de caractère dans une chaîne donnée

Matches

Teste si la chaîne donnée correspond à un pattern d'une « regular expression ».

CollectionAssert

AreEqual
AreNotEqual
AreEquivalent
AreNotEquivalent

Compare les deux collections fournies. Notez que la notion d'égalité traduit le fait que tous les éléments sont bien égaux (égalité de valeur et non de référence) et ce dans le même ordre et quantité.
La notion d'équivalence vérifie uniquement que tous les éléments sont présents (ordre non important).

AllItemsAreUnique
AllItemsAreNotNull
AllItemsAreInstancesOfType

Teste les différents éléments de la collection fournie

Contains
DoesNotContain

Teste la présence / l'absence d'un élément particulier dans la collection fournie

IV-B-2 - Vérifier qu'une exception a bien été lancée

Il peut être important de vérifier qu'un cas d'exception a bien été traité, et donc de vérifier qu'un process lance une exception. On peut pour cela utiliser l'attribut ExpectedException.

Utilisation de l'attribut ExpectedException

public class ClasseATester
{
public static double Divide(int numerateur, int denominateur)
{
return numerateur / denominateur;
}
}

[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void TestDivide_DivideByZero()
{
ClasseATester.Divide(2, 0);
}

IV-B-3 - Vérifier qu'une méthode s'exécute dans un temps raisonnable

Il peut être important de vérifier qu'un test s'est bien déroulé dans un laps de temps donné, principalement pour des tests de performance ou lors de refactoring ciblant l'amélioration des performances. Pour cela, l'attribut Timeout peut être utilisé.

Utilisation de l'attribut Timeout

[TestMethod]
[Timeout(2000)]
//Ce test échouera s'il se déroule en plus de 2000 millisecondes
public void TestSleep()
{
Thread.Sleep(3000);
}

Notez cependant qu'une marge doit être prise. Dans l'exemple précédent, même un Thread.Sleep(1500) aurait pu faire échouer le test, si celui-ci était le seul lancé. En effet, le timeout prend en compte le temps d'exécution complet du test soit :

- l'initialisation du moteur de test ;

- le déploiement des différents fichiers nécessaires au test ;

- l'initialisation du test lui-même (AssemblyInitialize, ClassInitialize…).

IV-C - Tester l'API non publique

Une des grandes questions lors de l'écriture de tests unitaires est « que doit-on tester ? ». La réponse la plus logique est que l'on doit tester ce que le client (le client de notre code) va utiliser, c'est-à-dire l'API publique.

Cependant, un des principes de l'orienté objet est de limiter le plus possible l'interface publique qui ne devrait se réduire qu'au strict nécessaire, le reste étant composé de code internal, protected et private.

On va donc également tester ce code à la visibilité plus réduite, même si ce code n'est a priori pas visible dans les classes de tests.

IV-C-1 - Tester les membres internal

IV-C-1-a - Présentation et utilisation

Imaginons que l'on ait une classe internal dans notre projet et que l'on veuille la tester. On peut alors utiliser l'attribut InternalsVisibleToAttribute.

Cet attribut se spécifie au niveau de l'assembly contenant le code à tester (typiquement dans le fichier AssemblyInfo) et permet à une autre assembly (celle contenant le code de test) de voir tous les membres internal comme s’ils étaient publics.

En supposant la solution suivante (Test.MyConsoleApplication référençant MyConsoleApplication) :

On peut déclarer la classe interne suivante :

Définition d'une classe internal

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Test.MyConsoleApplication")]
namespace MyConsoleApplication
{
internal class MaClasseInterne
{
internal void MaMethodeInterne()
{ }
}
}

Et on peut alors la tester de la façon suivante, comme si la classe était publique :

Déclaration de l'attribut InternalsVisibleToAttribute

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyConsoleApplication;

namespace Test.MyConsoleApplication
{
[TestClass]
public class MaClasseInterneTest
{
[TestMethod]
public void MaMethodeTest()
{
MaClasseInterne maClasse = new MaClasseInterne();
maClasse.MaMethodeInterne();
//Autre code de test omis
}
}
}

IV-C-1-b - Utiliser l'attribut avec une DLL fortement signée

Si jamais on a une DLL signée (i.e. avec un strong name), nous allons également devoir signer notre DLL de test, puis spécifier sa clé publique lors de la déclaration de l'attribut.

Nous allons tout d'abord utiliser l'outil « sn » pour extraire la clé publique de notre DLL / exe. Cet outil est installé avec Visual Studio et peut être trouvé dans le répertoire « %ProgramFiles% \ Microsoft Visual Studio 8 \ SDK \ v2.0 \ Bin ». (13)

La syntaxe est la suivante :

Syntaxe de sn.exe

Sn -Tp « Dll Path »

Dans mon cas, l'exécution de la ligne de commande donne le résultat suivant :

Utilisation de l'outil sn.exe pour extraire la clé publique

C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin>sn -Tp
"c:\Projects\MyTestSolution\Test.MyConsoleApplication\bin\Debug\
Test.MyConsoleApplication.dll"

Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42
Copyright (c) Microsoft Corporation.  All rights reserved.

Public key is
00240000048000009400000006020000002400005253413100040000010001007b9e3798f117b7
8553f96e3c9bfae4fe533e1ad47dea71ea486b9f70a473570466d006cadce035730a1c96470e73
fad75c61cb90492825ba903c7b5aceafd2df8e57ab202b03687f7277e045c2d9b8085a446bbba6
c9c807617bf88052aab98c31fb6674afee59f9df5ce01cbe77c079c6eacc53287e6c26fd6b809b
df9ffce4

Public key token is ccc3404edeae0195

On pourra alors mettre à jour l'attribut pour introduire la clé publique.

Spécification d'une clé publique avec « InternalsVisibleToAttribute »

[assembly: InternalsVisibleTo("Test.MyConsoleApplication,
PublicKey=0024000004......809bdf9ffce4")]

IV-C-2 - Tester les membres private et ou protected

IV-C-2-a - Présentation

De même, et pour les mêmes raisons, il peut être très intéressant de tester des méthodes non private ou protected.

Dès la version 2005 de Visual Studio, Microsoft a mis à notre disposition les « accesseurs » : il s'agit en fait de classes générées permettant d'atteindre (via réflexion) le code non public.

Cette fonctionnalité a fortement évolué depuis 2005 :

VS 2005

Possibilité de générer un accesseur sur un type en particulier: un fichier « .cs » (ou « .vb ») est alors généré dans le projet de test. Il s'agit d'un fichier lisible, sans aucune vérification à la compilation. En effet dans la mesure où l'on se base sur de la réflexion, on ne manipule que des strings, non liés aux types ou méthodes qu'ils reflètent. Ainsi pour chaque mise à jour du code original, il faut régénérer les accesseurs.Il y a de plus de nombreux cas pour lesquels il est impossible de générer des accesseurs, principalement dès que l'on joue un peu avec les génériques.

VS 2008

Introduction de la notion de « Shadow ». Visual Studio génère maintenant un fichier d'extension « .accessor ». Ce simple fichier texte permettra de générer à la compilation des accesseurs pour tous les types de la DLL que l'on shadow.Une nouvelle DLL, appelée NomDllOrignal_Accessor.dll est alors généré à chaque compilation. Il n'y a plus aucun risque d'avoir des accesseurs qui ne sont plus à jour par rapport au code original. La majeure partie des problèmes liés aux génériques est corrigée.

VS 2008 SP1

Seuls quelques problèmes persistent lors de l'utilisation d'interfaces génériques, lorsque le type possède des contraintes vers des types génériques.

IV-C-2-b - Création d'un accesseur via Visual Studio

Imaginons maintenant que je rajoute un fichier dans mon projet avec la définition de certaines classes ou membres privates.

Pour générer un accesseur, il suffit :

- d'ouvrir un des fichiers du projet pour lequel on veut générer un accesseur ;

- clic droit sur le code et choisir « Create Private Accessor », puis de choisir le nom de la DLL de test vers laquelle on veut générer l'accesseur ;

- nous avons maintenant un accesseur dans notre projet de test.

IV-C-2-c - Utilisation d'un accesseur

Si nous avons écrit le code suivant :

Classes possédant des membres privés

namespace MyConsoleApplication
{
public class MaClassePossedantDesMembresPrivate
{
private void MaMethodePrivee() { }

private static void MaMethodeStatique() { }
}

public class ClasseAvecConstructeurPrivate
{
private ClasseAvecConstructeurPrivate(int i) { }
}
}

Nous pouvons commencer par écrire un test pour appeler les membres de la classe MaClassePossedantDesMembresPrivate.

Utiliser un accesseur pour appeler des méthodes privées

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyConsoleApplication;

namespace Test.MyConsoleApplication
{
[TestClass]
public class MaClassePossedantDesMembresPrivateTest
{
[TestMethod]
public void MonTest()
{
MaClassePossedantDesMembresPrivate maClasse
= new MaClassePossedantDesMembresPrivate();

//1. Créer un accesseur pour appeler un membre d'instance
MaClassePossedantDesMembresPrivate_Accessor accessor
= MaClassePossedantDesMembresPrivate_Accessor.AttachShadow(maClasse);
accessor.MaMethodePrivee();

//2. Utiliser l'accesseur pour appeler un membre static
MaClassePossedantDesMembresPrivate_Accessor.MaMethodeStatique();
}
}
}

Ici on veut tester une classe instanciable. On peut donc créer une instance en utilisant le constructeur standard, et on utilise alors la méthode AttachShadow pour créer un accesseur qui nous permet d'accéder à l'instance qu'on lui fournit.

Toutes les méthodes non publiques sont alors disponibles sur cette instance d'accesseur.

En ce qui concerne les membres statiques, ils sont simplement disponibles sur le type de l'accesseur même.

Notez que la DLL de l'accesseur suit fidèlement la structure de la DLL originale, et que l'on retrouve exactement les mêmes namespace.

Si l'on veut tester un type en utilisant son constructeur privé, nous pouvons alors simplement utiliser le constructeur de l'accesseur comme suit :

Utiliser un accesseur pour appeler un constructeur privé

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyConsoleApplication;

namespace Test.MyConsoleApplication
{
[TestClass]
public class ClasseAvecConstructeurPrivateTest
{
[TestMethod]
public void MonTest_CtorPrivee()
{
//On utilise le constructeur de l'accesseur pour utiliser un constructeur privé
ClasseAvecConstructeurPrivate_Accessor accessor
= new ClasseAvecConstructeurPrivate_Accessor(100);

//Utilisation de la propriété Target pour accéder à l'instance
ClasseAvecConstructeurPrivate instance
= accessor.Target as ClasseAvecConstructeurPrivate;
}
}
}

IV-C-2-d - Création d'un accesseur dans le fichier de projet

Comme nous l'avons vu, Visual Studio nous permet de créer facilement une DLL d'accesseurs. Cependant, ceci n'est possible que dans une DLL de test.

Supposons le scénario suivant : vous avez plusieurs DLL de tests, qui contiennent du comportement commun que vous voulez centraliser. Vous allez donc créer une « Class Library » qui va contenir ce comportement commun.

Cependant vous allez avoir besoin de travailler avec certaines classes d'accesseurs.

Dans ce cas, Visual Studio ne vous permet pas de créer un accesseur via l'interface graphique, car votre DLL cible est une « Class Library » et non une DLL de test. Il est cependant possible de le faire en travaillant directement dans votre fichier de projet « .csproj » ou « .vbproj ».

- Ajoutez un nouveau répertoire dans votre projet et nommez-le « Test References ».

- Ajoutez un fichier texte dans ce répertoire et nommez le « MonNomDeProject.accessor » où MonNomDeProjet est le nom du projet pour lequel vous voulez créer un accesseur.

- Éditez ce fichier pour y ajouter deux lignes :

- sur la première ligne : le nom de la DLL/exe pour laquelle vous voulez créer un accesseur, extension comprise (par exemple : MonProjet.dll) ;

- sur la deuxième ligne : « Desktop ».

- Cliquez droit sur votre projet et choisissez « Unload Project ».

- Cliquez droit sur votre projet et choisissez « Edit xxx.csproj ».

- Ajoutez un nouvel ItemGroup avec les informations de l'accesseur (voir ci-dessous). Notez que cet ItemGroup doit se situer au même niveau que les autres éléments ItemGroup.

Contenu du fichier MyConsoleApplication.accessor

MyConsoleApplication.exe
Desktop

Définition de l'ItemGroup dans le fichier de projet

<ItemGroup>
<Shadow Include="Test References\MyConsoleApplication.accessor" />
</ItemGroup>

Vous pouvez alors recharger votre projet, et vous pourrez alors accéder à votre accesseur comme si vous étiez dans votre DLL de test.

Attention, il va de soi que les accesseurs ne doivent être utilisés que dans un contexte de test. En aucun cas vous ne devriez ajouter un tel accesseur dans une DLL de production.
Si du code de test peut faire abstraction du modèle objet encapsulé tel qu'il a été prévu et encapsulé, le code de production doit scrupuleusement suivre les règles de la programmation orientée objet.

V - Tests unitaires : Exécuter des tests avec MsTest

V-A - Exécution via Visual Studio

Dans toutes les versions, Visual Studio nous propose les onglets « Test View » et « Test List Editor ». (14)

On peut l'activer via le menu « Test / Window / Test View ». Il suffit alors de sélectionner un test et de choisir « clic droit / Run » ou « clic droit / Debug ».

Notez que Visual Studio 2008 propose aussi plusieurs touches de raccourci pour lancer les tests :

Touches de raccourci pour MsTest

Run

Debug

Signification

Ctrl + R, A

Ctrl + R, Ctrl + A

Tous les tests

Ctrl + R, D

Ctrl + R, Ctrl + D

Tous les tests du dernier « test run »

Ctrl + R, F

Ctrl + R, Ctrl + F

Tous les tests cochés dans le dernier « test run » (donc par défaut, tous les tests échoués du dernier « run »)

Ctrl + R, T

Ctrl + R, Ctrl + T

Tous les tests du contexte courant(TestMethod, TestClass, Namespace, projet, tout dépendant de la position du curseur)

Ctrl + R, C

Ctrl + R, Ctrl + C

Tous les tests de la TestClass courante

Ctrl + R, N

Ctrl + R, Ctrl + N

Tous les tests du Namespace courant

Nous détaillerons ci-après l'utilisation des vues « Test View » et « Test List Editor ».

V-B - Exécution en ligne de commande

On peut également choisir d'exécuter MsTest en ligne de commande. Il est pour cela préférable de définir le chemin d'accès à MsTest dans la variable d'environnement %Path%.

On pourra ainsi lancer les tests à partir du répertoire de la DLL de test ce qui nous évitera de trop longues commandes.

Vous trouverez ci-dessous plusieurs exemples pour exécuter MsTest.

V-B-1 - Exécution d'une liste de test

Lors de l'ajout de tests dans un projet, un fichier « .vsmdi » est aussitôt ajouté. C'est lui qui stocke les différentes listes de tests du projet. Si l'on désire lancer une liste de test particulière (voir ci-après pour la création d'une liste de test), on peut alors utiliser la commande suivante :

MsTest : Exécution de tous les tests de la liste de test « Itération3.3 »

Mstest /testmetadata:testList1.vsmdi /testlist:iteration3.3

V-B-2 - Exécution de tous les tests d'une DLL

Si l'on ne travaille pas avec une liste de test, il est possible de lancer tous les tests (c'est-à-dire toutes les TestMethod des TestClass d'une DLL). Pour cela, on utilise le switch « testcontainer ».

MsTest : Exécution de tous les tests contenus dans la DLL de test spécifiée

Mstest /testcontainer:maDllDeTest.dll

Notez que « testcontainer » peut être précisé plusieurs fois, si on veut lancer les tests de plusieurs DLL différentes.

V-B-3 - Exécution de certains tests d'une DLL

Si on veut filtrer les tests que l'on veut exécuter, alors on peut utiliser le switch « test ».

MsTest : Voir source précédente + restriction aux tests dont le nom est NomTest

Mstest /testcontainer:maDllDeTest.dll /test:NomTest

Notez que dans ce cas, « NomTest » peut être le nom de la TestClass ou de la TestMethod.

Cependant « NomTest » ne doit pas forcément être le nom exact. En effet, si vous précisez « Test », tous les tests (TestClass ou TestMethod) s'appelant « *Test* » seront lancés, soit par exemple les tests « CeciEstUnTest » et « TestSurLesEntiers ».

V-B-4 - Configuration supplémentaire

On peut également spécifier un fichier de configuration à utiliser. Se reporter à la section Configuration des tests pour plus d'informations sur les fichiers de configuration.

MsTest : Voir source précédente + spécification du fichier de config à utiliser

Mstest /testcontainer:maDllDeTest.dll /test:NomTest
/runconfig:mytestrunconfig.testrunconfig

On peut également spécifier l'emplacement du fichier de résultat d'exécution des tests. Il s'agit d'un fichier d'extension « .trx » qui pourra être ouvert dans Visual Studio.

MsTest : Voir source précédente + Spécification du fichier de résultat

Mstest /testcontainer:maDllDeTest.dll /test:NomTest
/runconfig:mytestrunconfig.testrunconfig
/resultsfile:c:\temp\results.trx

Il est à noter cependant que toute exécution de test produira un fichier de résultat « .trx », que celui-ci soit fixé dans la ligne de commande ou non. Ce fichier - qui sera généré par défaut dans un répertoire « TestResults » situé dans le répertoire d'exécution - peut être importé dans Visual Studio afin d'avoir plus d'informations sur les erreurs survenues, soit en passant par le menu « File / Open / File », soit en passant par la fenêtre « Test Results » et par la commande « Import Test Results ».

Notez également que MsTest utilise un code de retour signifiant :

- 0 : tous les tests exécutés ont réussi ;

- 1 : au moins un test a échoué, ce qui signifie :

- une exception a été levée dans le corps du test (et non « catchée » par l'attribut ExpectedException),

- le test est parti en timeout,

- le test a échoué (Assert.Fail ou un autre Assert a échoué),

- le test a lancé un Assert.Inconclusive,

- le test a réussi, mais l'exécution des tests a été stoppée.

VI - Tests unitaires : Configuration des tests

Lorsque l'on crée un projet de test dans une solution, Visual Studio ajoute aussitôt un fichier d'extension « .testrunconfig ». Il s'agit du fichier de configuration pour l'exécution des tests.

Notez qu'il est bien sûr tout à fait possible d'en rajouter d'autres, de façon à avoir des configurations différentes pour plusieurs DLL de test.

VI-A - Définition du nom des « tests runs »

Lors de l'exécution de tests, on parle de « test run ». Chacun d'entre eux possède un nom qui par défaut est « UserName@ServerName DateAndTime ».

Il peut être très utile de définir un autre nom à vos « test run », principalement si vous lancez plusieurs « test run » différents lors d'un build.

Définition du nommage des tests runs

VI-B - Définition de la couverture de code

VI-B-1 - Paramétrage

On peut également, via l'onglet « Code Coverage », définir pour quelles DLL nous mesurerons la couverture de code lors de l'exécution des tests.

Il suffit pour cela :

- de compiler la solution dans la configuration ;

- de cliquer sur l'onglet « Code Coverage », puis sur « Add Assembly » pour sélectionner la DLL souhaitée.

On pourra laisser les autres options par défaut.

Définition des DLL pour lesquelles on veut de la couverture de code

VI-B-2 - Visualisation des résultats

Pour visualiser le résultat de la couverture de code, il suffit de faire un « clic droit » sur les résultats de tests, et de demander « Code Coverage Results ».

Visualisation du résumé de couverture de code

On pourra alors voir un résumé - pour tous les projets couverts - de la couverture de code, autorisant de descendre au niveau de chaque fonction.

Il sera alors possible de double cliquer sur une méthode, de façon à visualiser la partie de code qui est couverte (en bleu) et celle par laquelle aucun test ne passe (en rouge).

Visualisation de la coloration de la couverture de code

Attention cependant : si vous demandez dans votre fichier .testrunconfig de couvrir trois DLL, mais que seules deux d'entre elles possèdent des tests unitaires, on pourrait logiquement s'attendre à que cette troisième DLL apparaissent dans le résultat de la couverture de code avec une couverture à 0 %.
Cependant les DLL couvertes à 0 % n'apparaissent pas dans les résultats. Le pourcentage global de couverture de code peut donc s'avérer trompeur.

VI-B-3 - Quel pourcentage rechercher ?

Je ne vais pas ici rentrer dans un débat sur le pourcentage de couverture de code à obtenir, mais je tenais à faire une courte mise en garde.

Si tous les experts s'accordent à dire qu'un projet couvert à 10 % est objectivement un projet à risque, a contrario, un projet couvert à 80 ou 100 % ne doit pas donner l'impression d'un projet sûr.

En effet une méthode couverte à 100 % signifie uniquement que du code de test passe par toutes les branches possibles de la méthode. Cela ne garantit en rien que des assertions existent pour toutes les branches possibles.

Il faut davantage s'attarder sur la qualité et la pertinence des tests unitaires que de se fixer un taux arbitraire de couverture de code à atteindre.

VI-C - Déployer des fichiers

VI-C-1 - Paramétrage

Certains tests peuvent avoir besoin de ressources (fichier textes, scripts…) lors de leur exécution. Il est possible via la configuration de définir des fichiers à déployer.

Pour cela, il suffit de se rendre dans l'onglet « Deployment » et de sélectionner « Add File » ou « Add Directory », tout en s'assurant bien sûr que la case à cocher « Enable deployment » est bien cochée.

Définition des fichiers à déployer

VI-C-2 - Où les fichiers sont-ils déployés ?

À chaque fois que les tests s'exécutent, un répertoire avec le nom du « test run » est créé, avec à l'intérieur un répertoire « Out ».

Outre les différentes DLL à tester, c'est ici que l'on retrouve les fichiers déployés.

Emplacement de déploiement des fichiers

Attention au nommage des fichiers et répertoires! Si vous demandez de déployer un répertoire, c'est ce répertoire au complet qui sera copié dans le répertoire « Out ».
Dans tous les cas, si vous déployez deux fichiers ou deux répertoires avec le même nom, l'un des deux sera perdu. Un simple avertissement dans un fichier de log vous avertira de votre erreur.

VI-C-3 - Précaution d'emploi avec les versions plus anciennes de Visual Studio

Notez que lors de l'ajout d'un répertoire, Visual Studio va automatiquement ajouter un « \ » à la fin du répertoire.

Dans les premières versions de Visual Studio 2005 (sans service pack), cela pouvait poser problème lors de l'exécution des tests, considérant que le fichier n'était pas valide. Si c'est le cas, il suffit d'éditer le testrunconfig en XML afin de supprimer le dernier « \ ».

VI-D - TimeOut

Le dernier onglet « TimeOut » nous permet de définir un délai maximal avant lequel un test non terminé sera considéré comme abandonné ou échoué.

Ce délai est défini par défaut à 30 minutes.

VII - Tests unitaires : Gestion des tests

Comme expliqué précédemment lors de la présentation de l'exécution des tests, il est possible de gérer les tests via deux outils, que sont les « Test View » et le « Test List Editor ».

VII-A - Présentation de la « Test View »

Cet outil minimaliste est à mon sens le plus simple pour « gérer » ses tests. C'était de plus le seul disponible dans les versions « Team Edition For Software Developpers » 2005.

Dans cette fenêtre, on pourra visualiser l'intégralité des tests, en filtrant sur certains critères, ou en regroupant les tests.

Visualisation de la « Test View », regroupant les tests par leur nom de classe

Avec cette fenêtre minimale, et une bonne politique de nommage de tests, il est tout fait possible de gérer efficacement plusieurs milliers de tests.

VII-B - Présentation de la « Test List Editor »

Lorsque l'on crée un projet de test dans une solution, Visual Studio ajoute aussitôt un fichier d'extension « vsmdi ». Il s'agit du « Test Metadata File ». C'est un fichier XML (relativement complexe et utilisant des GUID pour référencer les éléments) qui stocke toutes les listes de tests que vous pouvez créer.

Une liste de tests n'est alors simplement qu'un regroupement logique de tests. Cette logique étant entièrement laissée à votre choix.

Il n'existe cependant pas de liste par défaut et donc lorsque vous créez un test, il sera repris sous la « pseudoliste » « Tests not in a list ». Ceci est très important à retenir, car si votre logique d'exécution des tests se base sur des listes, vos nouveaux tests ne seront pas exécutés, car ils n'apparaissant dans aucune liste.

Vue des listes de tests dans le « Test List Editor »

Notez que par défaut, c'est le principe de liste de test qui est utilisé pour le build automatisé. C'était d'ailleurs le seul disponible pour le Team Foundation Server 2005. La version 2008 permet également d'exécuter les tests de DLL, sans utiliser les listes de test.

VIII - Tests unitaires : Attributs additionnels

VIII-A - Attributs de comportement

VIII-A-1 - Ignorer un (ou des) test(s)

Il est possible d'ignorer certains tests en utilisant l'attribut IgnoreAttribute. Il peut être placé sur une méthode ou sur une classe.

Si on le place sur une méthode, alors ce test ne sera pas exécuté.

Si on le place sur une classe, alors ce sont tous les tests de cette classe qui seront ignorés.

Utilisation de l'attribut « IgnoreAttribute »

[TestClass]
[Ignore]
public class MaClasseDeTestAIgnorer
{  }

[TestClass]
public class AutreClasseDeTest
{
[Ignore]
public void MonTestAIgnorer()   {   }
}

Attention, il est également possible de désactiver un test. Pour cela il faut :
- visualiser le test dans la « Test View » ou dans le « Test List Editor » ; - clic droit sur le test et « Propriétés » ;
- indiquer « false » dans « Test Enabled ».
Un test ignoré n'apparaitra pas dans les listes de tests, alors qu'un test désactivé apparaitra grisé.

VIII-A-2 - Déployer des fichiers

Nous avons vu que l'on pouvait déployer des fichiers en utilisant les fichiers de configuration des tests. Il est également possible de le faire par attribut. Ceci a deux avantages :

- ne déployer les fichiers que lors de l'exécution du test qui en a besoin (et non avant que le premier test ne soit exécuté) ;

- permettre de spécifier à quel endroit nous voulons déployer les fichiers (le répertoire de déploiement sera un sous-répertoire du répertoire de sortie standard)

Utilisation de l'attribut « DeploymentItemAttribute »

[TestMethod]
[DeploymentItem("Fichier.txt")]
public void MonTest()   {   }

[TestMethod]
[DeploymentItem("Fichier.txt", "RépertoireSortie")]
public void MonAutreTest()   {   }

Attention, vous ne pouvez utiliser dans cet attribut que des strings (« Ceci est une string » ) ou des verbatim string ( @« Ceci est une verbatim string »).
Vous ne pouvez en effet pas utiliser de constantes. Les tests étant exécutés par réflexion, les constantes que vous aurez utilisées ne seront pas encore initialisées lors de « l'exécution » de l'attribut.

Pour plus d'informations sur cet attribut, vous trouverez tous les renseignements nécessaires dans la bibliographie : « DeploymentItemAttribute. »

VIII-B - Attributs informationnels

Certains autres attributs existent également. Ils ne seront pas utilisés pas Visual Studio, mais peuvent permettre d'ajouter des informations supplémentaires, informations qui pourront être exploitées par le développeur ou via code.

VIII-B-1 - Identifier le créateur du test

On peut utiliser l'attribut OwnerAttribute pour identifier le créateur du test.

Utilisation de l'attribut « OwnerAttribute »

[TestMethod]
[Owner("Pierre-Emmanuel Dautreppe")]
public void MonTest()   {   }

VIII-B-2 - Donner une priorité aux tests

On peut utiliser l'attribut PriorityAttribute pour définir la priorité d'exécution du test. Notez cependant que cet attribut n'est pas utilisé par le moteur de test et n'est présent qu'à des fins d'extension par le développeur.

Pour permettre d'établir une notion d'ordre d'exécution, Visual Studio a introduit les OrderedTests. Ils ne seront cependant pas couverts dans ce document.

Utilisation de l'attribut « PriorityAttribute »

[TestMethod]
[Priority(10)]
public void MonTest()   {   }

VIII-B-3 - Ajouter des propriétés aux tests

On peut utiliser l'attribut TestPropertyAttribute pour ajouter une propriété spécifique à un test. Cette propriété n'est pas utilisée par le moteur de test, mais permet (après un rebuild) de voir une nouvelle propriété dans l'onglet « Properties » après avoir sélectionné le test dans le « Test Manager » ou dans le « Test View ».

Cette propriété pourra être récupérée dans le test en utilisant la réflexion, comme pour n'importe quel attribut.

Utilisation de l'attribut « TestPropertyAttribute »

[TestMethod]
[TestProperty("MonNomDePropriete", "MaValeur")]
public void MyTest()   {   }

IX - Analyse Statique de Code : Présentation

IX-A - Activer l'analyse statique de code

L'analyse statique se définit et s'active au niveau du projet. Pour ce faire, il suffit de faire un clic droit sur le projet, et de choisir « Properties ». On peut alors choisir l'onglet « Code Analysis » et activer la case « Enable Code Analysis ».

Vue de paramétrage de l'analyse statique de code

IX-B - Lancer l'analyse statique de code

Pour lancer l'analyse statique de code, il suffit de cliquer droit sur le projet et de choisir « Run Code Analysis ».

Visualisation du résultat de l'analyse statique de code

IX-C - Explication du nommage des règles

Chaque règle est caractérisée par

- une catégorie (par ex. : Microsoft.Naming) ;

- un code / CheckId (par ex. : CA1707) ;

- un nom de règle, souvent très parlant et proche du texte affiché dans les propriétés du projet (par ex. : IdentitifiersShouldNotContainUnderscores) ;

- une indication spécifiant si modifier le code pour corriger l'erreur entraînera des changements brisant la compatibilité (Breaking Change).

Notez que chaque règle est décrite avec précision dans la MSDN. Pour y accéder, il suffit de faire une recherche dans la MSDN en utilisant le code de l'erreur (eg CA1707) ou de simplement appuyer sur « F1 » en sélectionnant l'erreur / avertissement généré par l'analyse statique de code.

On retrouvera notamment des explications sur la raison de mise en application de cette règle, et souvent des exemples de code, violant ou non la règle.

Certaines des règles sont décrites dans la MSDN comme étant « à corriger si applicable ». En effet, cette analyse contient des règles standards dont le suivi va dépendre de l'implémentation mise en place.

IX-D - Paramétrage des règles

Vous pouvez bien entendu paramétrer l'analyse selon le projet sur lequel vous travaillez. Pour cela, vous pouvez :

- supprimer définitivement (pour le projet courant) l'analyse d'une règle donnée ;

- spécifier qu'une règle donnée lève soit un avertissement, soit une erreur.

IX-D-1 - Paramétrage via l'interface de Visual Studio

Ce paramétrage s'effectue dans les deux cas dans la page de propriétés du projet.

Ignorer des règles d'analyse statique de code

Dans la figure ci-dessus, on spécifie qu'aucune des règles d'interopérabilité ne sera vérifiée. De même on ne vérifiera pas la règle CA1700 alors que la règle CA1701 lèvera une erreur et non avertissement.

IX-D-2 - Paramétrage via fichier de projet

Pour le moment les règles d'analyses sont propres à un projet et il n'est pas possible de les importer de projet en projet ou encore de les spécifier sur une solution.

La seule solution pour pallier cette lacune serait de créer son propre template de projet, template qui pourrait alors comporter la liste des erreurs qui sont ignorées dans ce projet. En effet, le paramétrage des règles d'analyse statique de code se reflète au niveau du fichier de projet (.csproj ou .vbproj).

Il s'agit en effet d'une propriété CodeAnalysisRules dont le format est le suivant :

CodeAnalysisRules = {Règle}[;{CodeAnalysisRules}]

Où

Règle = {Symbole}{Nom Catégorie}#{CheckId}

Où

Symbole =

-

pour ignorer une règle

+!

pour demander de lever une erreur si la règle n'est pas respectée (et non un simple avertissement)

Nom Catégorie

est le nom de la catégorie de l'erreur (ex Microsoft.Naming)

CheckId

est le code de l'erreur (ex CA1707)

Spécifier dans un projet qu'une règle est ignorée

<PropertyGroup>
<CodeAnalysisRules>-Microsoft.Design#CA2210</CodeAnalysisRules>
</PropertyGroup>

Spécifier dans un projet qu'une règle lèvera une erreur

<PropertyGroup>
<CodeAnalysisRules>+!Microsoft.Design#CA2210</CodeAnalysisRules>
</PropertyGroup>

Dans la première source, on spécifie qu'une règle spécifique est ignorée : ici la règle CA2210 de la catégorie « Microsoft.Design ».

Dans la seconde source par contre, on spécifie que cette même règle sera vérifiée et qu'elle provoquera une erreur au lieu d'un avertissement.

Si on veut spécifier plusieurs règles, il suffit de les séparer par un « ; ».

Notez cependant qu'il est très délicat de fixer des règles interdites pour tous les types de projets à venir dans la mesure où tous répondront à des besoins spécifiques.

X - Analyse Statique de Code : Corriger les erreurs

X-A - Corriger une erreur

Lorsque votre projet a été paramétré et que vous lancez l'analyse statique de code, un certain nombre d'avertissements et/ou d'erreurs vont être levés. (15)

Vous avez trois solutions pour corriger un avertissement/erreur ;

- modifier les paramètres de l'analyse statique de code pour ignorer l'erreur levée (voir la section Paramétrage des règles) ;

- corriger le code pour qu'il satisfasse la règle de l'analyse statique de code. Pour cela, on peut se reporter à la documentation et aux exemples de la MSDN ;

- désactiver la vérification de l'analyse de code pour l'élément sélectionné.

Comme chaque règle est identifiée de façon unique, vous pouvez supprimer la vérification pour une méthode spécifique. Pour ce faire, vous pouvez cliquer (bouton droit) sur le message et choisir « Suppress Message(s) ».

Vous pouvez alors choisir l'emplacement où sera stockée votre suppression : au-dessus de la méthode elle-même, ou dans un fichier de suppression global.

Suppression de la vérification d'une règle d'analyse statique via attribut

[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Naming", "CA1706:ShortAcronymsShouldBeUppercase")]

X-B - Corriger plusieurs erreurs

Il n'est pas possible de supprimer plusieurs erreurs avec un seul attribut « SuppressMessageAttribute ». En effet, cela permettrait certes de corriger plusieurs erreurs existantes, mais aussi de corriger toutes les erreurs du même type à venir, ce qui pourrait avoir des conséquences dramatiques en termes de sécurité, performance…

Si une erreur peut être ignorée à l'heure actuelle - on connaît précisément la raison pour laquelle on a écrit le code et on peut donc accepter qu'il brise une règle - on ne peut être garant a priori du code futur.

X-C - Regrouper les suppressions dans un même fichier

X-C-1 - Syntaxe du fichier de suppression global

Cependant certains éléments du code ne sont pas liés à un emplacement (fichier) fixe. C'est typiquement le cas d'un namespace. Par conséquent, il ne serait pas judicieux d'ajouter un attribut « SuppressMessageAttribute » dans un fichier plutôt que dans un autre.

Visual Studio nous offre la possibilité de créer un fichier global qui peut contenir une liste de suppressions d'erreurs. Notez que ce n'est que depuis la version 2008 que Visual Studio nous laisse le choix du positionnement des erreurs que l'on supprime.

Ce fichier – nommé « GlobalSuppressions.cs » ou « GlobalSuppressions.vb » se trouve à la racine du projet concerné et va contenir par exemple les lignes suivantes :

Suppression d'erreurs - Correction via fichier global

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming",
"CA1705:LongAcronymsShouldBePascalCased",
Scope = "namespace",
Target = "MyRootNamespace")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1805:DoNotInitializeUnnecessarily",
Scope = "member",
Target = "MyRootNamespace.MyClass..ctor()")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2215:DisposeMethodsShouldCallBaseClassDispose",
Scope = "member",
Target = "MyRootNamespace.MyClass.Dispose(System.Boolean):System.Void")]

On retrouve la même syntaxe que dans les attributs « traditionnels », mais on retrouve deux paramètres nommés supplémentaires : « Scope » et « Target ».

Scope nous permet de préciser le type d'objet que l'on cible. En pratique seuls namespace et member seront utilisés.

Target quant à lui spécifie l'objet pour lequel on veut supprimer l'erreur. On doit toujours préciser le nom complet, namespace inclus.

Notez que pour une méthode, on doit utiliser la syntaxe « Type.NomMethode([TypeParamètre1[, TypeParametre2[…]]]):TypeRetour » ou chaque « Type » correspond en fait a son FullName, soit « Namespace[.ChildNamespace[…]].Type ».

X-C-2 - Préciser des méthodes avec noms réservés en IL

Certaines méthodes du framework ont des noms réservés lors de la compilation en code IL. Pour connaître ces différents noms, il « suffit » de connaître le code qui est généré par le compilateur. ILDASM ou Reflector.NET pourra donc vous montrer le nom correct de votre méthode.

Pour citer quelques exemples, on pourra noter :

Liste de quelques méthodes aux noms générés par le compilateur

Nom

Définition

.ctor()
.ctor([Parameters])

Constructeur de la classe

public class Class1
{
public Class1() { }
}

.cctor()

Constructeur statique de la classe

public class Class1
{
static Class1() { }
}

Finalize():System.Void

Destructeur de la classe

public class Class1
{
~Class1() { }
}

op_Implicit
op_Explicit
op_Addition
op_Subtraction
op_Division
op_Multiply
op_Equality
op_Inequality
op_LessThan
op_GreaterThan
op_LessThanOrEqual
op_GreaterThanOrEqual
op_LeftShift.

Quelques-uns des opérateurs que l'on peut surcharger sur une classe
Dans les exemples ci-dessous, l'implémentation sera toujours omise.

public class Class1
{
public static implicit operator Class1(string b) {}
public static explicit operator Class1(double b) {}
public static Class1 operator +(Class1 a, Class1 b) {}
public static Class1 operator -(Class1 a, Class1 b) {}
public static Class1 operator /(Class1 a, Class1 b) {}
public static Class1 operator *(Class1 a, Class1 b) {}
public static Class1 operator ==(Class1 a, Class1 b) {}
public static Class1 operator !=(Class1 a, Class1 b) {}
public static Class1 operator <(Class1 a, Class1 b) {}
public static Class1 operator >(Class1 a, Class1 b) {}
public static Class1 operator <=(Class1 a, Class1 b) {}
public static Class1 operator >=(Class1 a, Class1 b) {}
public static Class1 operator <<(Class1 a, int i) {}
}

X-C-3 - Renommer le fichier de suppression global

Si vous désirez utiliser un autre nom de fichier, vous pouvez éditer le fichier de projet (.csproj / .vbproj) et ajouter la ligne suivante (où [name] est le nouveau nom du fichier - sans chemin):

Changer le nom du fichier de suppression global

<PropertyGroup>
<CodeAnalysisModuleSuppressionsFile>[name]</CodeAnalysisModuleSuppressionsFile>
</PropertyGroup>

XI - Analyse Statique de Code : Limitations

Le moteur d'analyse statique de code a bien évolué entre les versions 2005 et 2008 de Visual Studio.

La première version en effet ne détectait pas toujours les erreurs les plus simples si celles-ci étaient « cachées » dans des méthodes anonymes. Cependant ces erreurs ont été corrigées depuis.

À mon sens, et autant que je connaisse l'outil, je ne vois que deux limitations :

- la possibilité de définir facilement des règles standards sur une solution au complet, règles qui pourraient être affinées de projet en projet. Ces règles standards pourraient même être mises au niveau des settings de Visual Studio, et ainsi être exportées ;

- l'impossibilité de créer de nouvelles règles d'analyse statique de code, comme nous le permettait FxCop.

XII - Conclusion

Nous avons donc vu les bases de l'intégration continue, afin de mieux comprendre ce que cela nous apporte et pourquoi il est intéressant de la mettre en place sur nos propres projets.

Nous avons également vu en détail les deux premiers outils que nous pouvons utiliser dans le cadre de l'intégration continue : MsTest pour écrire des tests unitaires, et Static Code Analysis pour réaliser une analyse du code et le valider contre un certain nombre de règles préétablies.

Dans la partie suivante, nous aborderons deux autres outils indissociables de l'intégration continue dans le monde Microsoft.NET : MsBuild et TFSBuild. MsBuild nous servira à automatiser certaines étapes indispensables à la mise en place de notre intégration continue, tandis que TFSBuild est une surcouche de cet outil de façon à la mettre en place le plus simplement possible avec Visual Studio et Team Foundation Server.

Remerciements

Je tiens à remercier particulièrement les personnes suivantes :

- Thierry Thoua qui m'a poussé à reprendre cet article (j'avais écrit une première version en janvier 2008) pour le détailler et le remettre à jour ;

- Louis-Guillaume Morand a qui j'avais parlé de cet article et qui m'a incité à le publier sur Développez.com, tout en m'assistant pour les phases de mise au gabarit et d'apprentissage des outils propre au site Développez.com ;

- Thibaut Van Spaandonck qui a consacré un certain temps à la relecture de cet article et à la correction de coquilles et autres fautes d'orthographe ;

- Les différents rédacteurs de Développez.com qui ont mis à disposition les outils facilitant la transformation d'articles au format Développez.com.

Contact

Pour plus d'informations, vous pouvez me contacter (via le forum de Developpez.com) ou par mail sur pedautreppe[arobase]skynet.be.
N'hésitez pas à me contacter pour me signaler des imprécisions ou des erreurs dans cet article ainsi que si vous aimiez que je détaille davantage une des sections dans un article ultérieur.

Vous pourrez trouver également toute une série d'articles plus courts (et en anglais) traitant de .NET, des VSTO ou encore des méthodologies agiles sur mon blog

XIII - Annexes - Bibliographie

XIII-A - Data-Driven Testing

MSDN 2008

http://msdn.microsoft.com/en-us/library/ms182527.aspx

MSDN 2005

http://msdn.microsoft.com/en-us/library/ms182527(VS.80).aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vsetlt01/ html/a0322bc5-02c8-4f9f-af43-100a60b1bd28.htm

Section document

Tests unitaires : Écrire des tests avec MsTest

XIII-B - DeploymentItemAttribute

MSDN 2008

http://msdn.microsoft.com/en-us/library/microsoft.visualstudio. testtools.unittesting.deploymentitemattribute.aspx

MSDN 2005

http://msdn2.microsoft.com/en-us/library/microsoft.visualstudio. testtools.unittesting.deploymentitemattribute(VS.80).aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vsetlt04/ html/c77056c1-56e2-0705-4d71-e44d586caf77.htm

Section document

Déployer des fichiers

(1)Le fonctionnement d'un Source Control est évidemment plus complet et plus complexe que les deux fonctionnalités suscitées.

(2)Raison pour laquelle de nombreux projets des communautés proposent parmi leurs téléchargements non seulement les versions stables, mais également les « nightly builds » : versions intermédiaires, quotidiennes, qui ont été produites par le système d'intégration continue, et qui risquent d'être instables.

(3)Nous parlerons plus en détail de l'analyse statique de code lors de la présentation des outils de l'environnement Microsoft

(4)Afin de développer ses propres règles de validation, il est nécessaire d'utiliser le « Visual Studio SDK »

(5)Notez que cette version sera supprimée dans les versions 2010 de Visual Studio. Ses fonctionnalités seront déplacées dans la version « For Software Testers » . Toute personne ayant d'ailleurs acheté une version « For Software Developpers », peut également installer la version « For Database Professional »

(6)La « Team Suite » est la version intégrale, regroupant les différentes « Team Edition »

(7)Notez d'ailleurs que suite à la sortie de Visual Studio 2005, une nouvelle version de NUnit a été produite afin de suivre le plus possible la direction prise par Microsoft (déclaration des tests par attributs et non plus par convention de nommage) et donc permettre un temps d'adaptation d'un outil vers l'autre quasiment nul.

(8)Notez cependant que le fichier utilisé par Visual Studio est très complexe et dépasse largement le périmètre de ce document. Il est totalement déconseillé de le modifier sans avoir une parfaite connaissance de la syntaxe des commandes utilisées. Il sera cependant beaucoup plus simple pour un projet en particulier de travailler sur le fichier « .csproj » qui est lui-même un fichier de build.
Ces fichiers de compilation (d'extension « .targets ») sont présents dans le répertoire « %Windir%\Microsoft.NET\Framework\VersionFramework ».

(9)Notez que seules ces deux extensions seront reconnues par Visual Studio pour ajouter de la coloration syntaxique et de l'IntelliSense. Notez cependant que l'IntelliSense fournie dans ces types de fichiers est souvent approximative.

(10)On appelle changeset une liste de changements effectués dans le code pendant un commit

(11)On dépasse dans ce cas le contexte d'un test « unitaire »

(12)Dans le cas de l'utilisation d'Assert.Inconclusive, c'est une exception de type AssertInconclusiveException qui est lancée. Cette classe et AssertFailedException héritent toutes les deux de UnitTestAssertException.

(13)Notez que l'outil n'a pas été mis à jour avec Visual Studio 2008. C'est pourquoi vous le trouverez toujours dans le répertoire d'installation « Microsoft Visual Studio 8 » et non dans le répertoire « %ProgramFiles% \ Microsoft Visual Studio 9.0 \ SDK \ v3.5 \ Bin »

(14)Notez que le « Test List Editor » n'est disponible dans toutes les versions que depuis Visual Studio 2008. Seule la « Team Edition For Testers » la proposait parmi les versions 2005, sous le nom « Test Manager ».

(15)Notez d'ailleurs que l'analyse statique de code est limitée à 200 avertissements / erreurs. Il est donc important de bien paramétrer votre projet pour que les erreurs soient vraiment parlantes.
Cependant une bonne pratique est d'avoir toujours comme objectif zéro avertissement levé par l'analyse statique de code.