Depuis un certain temps je développe à fond avec le framework ASP.NET MVC. Il existe de nombreux livres et blogs à son sujet, le plus connu étant celui de Scott’Gu et son application NerdDinner. J’ai également envie de consacrer à ce Framework quelques posts en traitant d’autres sujets à côté comme par exemple : les tests unitaires que je présente dans ce post, l’architecture générale, plug-ins avec MEF, WF, etc. Pour le moment je vais faire une courte introduction aux tests unitaires dans ASP.NET MVC 2 avec NBehave, d’autres sujets seront introduits au fur et à mesure. J’essaierai de me concentrer si le sujet le permet, sur les nouveautés qui sont introduits avec la nouvelle version du framework ASP.NET MVC 2 mais ce n’est pas mon but ultime. Dans ce poste j’utilise la version MVC 2 mais cela s’applique également à la première version de MVC.

Pour rappel, un des points le plus importants du succès du Framework ASP.NET MVC est sa testabilité. Il a été construit pour simplifier les parties complexes des Web Forms tout en gardant la puissance et la flexibilité de ASP.NET classique. En bref ASP.NET MVC est fourni pour supporter les applications ASP.NET en utilisant le Model View Controller pattern de présentation. Le code du contrôleur est contenu dans une classe faiblement couplée aux dépendances alors que les pages web se sont transformées en des simples vues qui ne sont rien d’autre que les Template HTML rempli avec des objets passés par le contrôleur. Il n’y a plus de cycle de vie des évènements Postback donc le Viewstate n'est plus nécessaire.

Quand vous créez un nouveau projet ASP.NET MVC 2 sous Visual Studio 2010 (ou 2008), un assistant vous demande si vous voulez créer un projets de tests. Par défaut l’assistant vous propose d’utiliser “Visual Studio Unit Test” framework. Vous pouvez cependant ajouter d’autres framework de test comme par exemple NUnit ou MbUnit. (attention les version release de ces frameworks de tests ne sont pas encore disponible pour le CLR 4). Pour en savoir plus vous pouvez lire le poste qui décrit cette opération pour la version 2008 de Visual Studio: http://blogs.msdn.com/webdevtools/archive/2008/02/18/asp-net-mvc-test-framework-integration.aspx. Je n’ai pas testé par contre si cela fonctionne sous Visual Studio 2010. Je vous dirai juste que si vous voulez faire le test, partez des sources de NUnit et compilez le en version 4 de framework. Si quelqu’un fait le test je serais content d’avoir un retour pour mettre à jour ce billet.

Pour ma part j’ai toujours été habitué à utiliser NUnit et RhinoMocks pour réaliser mes tests unitaires. D’ailleurs j’utilise à fond le projet MvcContrib qui fourni pas mal de fonctionnalités supplémentaires comme par exemple:

  • Injection de dépendances
  • ViewData forement  typé
  • Validation helpers
  • nouveaux ActionResults (BinaryResult et XmlResult)
  • nouvaux filtrès
  • et beaucoup plus. Visitez la page de MvcContrib.

Mais puisqu’on parle des tests vous trouverez également une bibliothèque qui s’appelle le TestHelper qui vous fourni une Factory pour les contrôleurs ce qui permet de les initialiser correctement. Vous y trouverez par exemple :

  • HttpContext
  • HttpRequest
  • HttpResponse
  • HttpSession
  • Form
  • TempData
  • QueryString
  • ApplicationPath
  • PathInfo

Mais ne nous égarons pas. Nous devons réaliser les tests avec la librairie NBehave.

NBehave ? Qu’est-ce que c’est ?

Avant de parler de NBehave nous allons parler de BDD c’est-à-dire de Behaviour Driven Development. Je suis désolé si ma définition ne plaît pas aux puristes du domaine mais on peut parler du BDD en tant que “l’évolution” du bon vieux TDD (Test Driven Development). BDD englobe le TDD et le DDD (désolé pour tous ces acronymes :) ) donc Domain Driven Design. En bref, le but du BDD est de concentrer les efforts de développement sur l’obtention de la valeur ajoutée vérifiable pour le business. Le langage commun qui est mis en place permet de diminuer le gap entre le Business et la Technologie. Nous n’allons pas rentrer dans les détails du BDD mais si vous voulez en savoir plus je vous invite à consulter les sites suivants:

Concrètement nous pouvons résumer le processus BDD de la manière suivante:

Un utilisateur “business” (donc celui qui connaît le métier) travaille avec un business analyste afin d’identifier les besoins du métier (business). Ce travail est exprimé en tant qu’une Story (histoire) conforme à ce modèle:

As a Role

I Request a Feature

To gain a Benefit

 

Je ne sais pas si la traduction est parfaite mais en gros cela veut dire:

En tant que Rôle

Je demande une Fonctionnalité

Afin d’obtenir un Bénéfice

 

La personne qui tient le Rôle est la personne qui aura le Bénéfice de la Fonctionnalité demandée.

Vous-vous demandez certainement quel rapport a tout cela avec les tests et le développement ? Personnellement je me suis posé la même question et bien, vu que je ne suis pas l’expert du BDD (c’est une découverte) en cherchant un peu je suis tombé sur l’explication suivante :

“BDD défini les interactions et les rôles entre les objets. Cela encourage les développeurs à penser au Comportement d’un composant qu’ils développent, aux Rôles et aux Responsabilités des autres objets avec lesquels il interagi.”

 
The Story (une histoire)

Tout bon développement BDD doit commencer par une Story donc une histoire. Pour cela nous allons utiliser la classe du même nom qui sert à décrire les scénarios que nous allons tester. Je me conforme au template que je défini ci-dessus:

En tant qu’un utilisateur

Je veux accéder à la page d’accueil

Afin de voir le message d’accueil ‘Welcome to ASP.NET MVC!’

 
Le Scénario

Une Story est composée d’un ou plusieurs scénarios. Les scénarios s’expriment à l’aide des mots Given, When, Than. Nous allons écrire deux scénarios, une pour l’action Index() et l’autre pour About() toutes les deux définies dans le contrôleur HomeController.

Given (étant donné) que mon contrôleur est instancié

When (quand) je navigue à la page d’accueil

Than (alors) ma page d’accueille est instanciée est affichée

 

Passons maintenant à un peut de pratique.

Assez de parler ! Un petit exemple s’impose !

Pour cela nous allons créer une simple “ASP.NET MVC 2 Web Application”. Je pense que pour comprendre NBehave nous n’avons pas besoin de plus. On va commencer avec HomeController parce qu’il est plus facile que AccountController. Nous allons l’exploiter dans un deuxième exemple.

Préparer l’environnement

1. Dans Visual Studio 2010, créez-votre “ASP.NET MVC 2 Web Application”. Quand une fenêtre vous demandera de créer un projet de tests unitaires, cliquez sur “Non”. Nous allons utiliser à la place NUnit avec NBehave (désolé pour les fans de MSTest).

2. Ajoutez ensuite un nouveau projet “Class Library” à votre solution que vous allez appeler <nom de mon web application>.Tests. Comme vous-vous en doutez cela sera notre projet de tests. Créez ensuite votre classe de tests “HomeControllerTests” dans le dossier “Controllers” que vous créez également à la racine de ce projet.

3. Dans votre projet de tests ajoutez une référence à notre application web.

4. Vous devez également télécharger les sources de NUnit http://www.nunit.org/index.php?p=download et NBehave http://code.google.com/p/nbehave/source/checkout. Ces bibliothèques ne sont pas encore prête pour travailler avec le Framework 4.0 donc vous devez d’abord ouvrir les sources dans dans Visual Studio 2010, convertir les solutions et générer les assemblies pour le Framework 4.0. 

4. Ajoutez ensuite les références suivantes:

1
2
3
using NBehave.Narrator.Framework; 
using NBehave.Spec.NUnit;
using NUnit.Framework;

La première référence NBehave.Narrator.Framework importe l’API d’un “narrateur”. C’est une interface mimique un narrateur qui conte une histoire (un scénario). C’est cette classe qui permet d’utiliser le code pour raconter une histoire comme par exemple “En tant qu’un utilisateur, j’accède à la page d’accueil et je vois le message ‘Welcome to ASP.NET MVC!’”.

La deuxième référence NBehave.Spec.NUnit contient la définition de la classe SpecBase de laquelle dérive notre classe de test. C’est aussi un pont entre NUnit et NBehave. Je vais expliquer l’intérêt de la classe SpecBase dans un article à part mais pour le moment tout ce que vous avez à savoir ce que cette classe permet d’auto mocker avec RhinoMocks les dépendances.

Vous devez ajouter une troisième référence vers NBehave.Spec.Framewrok.

Attention: la technique montré ici est “old style”. C’est-à-dire, maintenant pour déclarer une Story il est conseillé de passer par les attributs des classes et des méthodes, c’est une manière déclarative. Pour la simplicité de l’exercice j’utilise la vieille technique à partir du code d’une manière impérative.

5. Décorer vos méthodes de test. Comme vous faites vos tests unitaires vous utilisez l’attribut [Test] pour décorer vos méthodes, mais pour les développeurs qui font du BDD ces attributs sont redéfinis comme ceci:

1
2
3
4
5
6
using Specification = NUnit.Framework.TestAttribute; 
using That = NUnit.Framework.TestAttribute;
using Describe = NUnit.Framework.CategoryAttribute;
using For = NUnit.Framework.CategoryAttribute;
using Wrote = NUnit.Framework.DescriptionAttribute;
using Should = NUnit.Framework.DescriptionAttribute;
La seule raison de redéfinir ces attributs est pour augmenter le lisibilité des tests.

6. Définissons maintenant notre Story. Ceci n’est pas un test unitaire mais uniquement une description de User Story. Pour cela nous allons utiliser fluent interface de NBehave, c’est-à-dire une interface ou chaque méthode retourne this, ce qui permet de faire le chaînage des méthodes. Comme je l’ai signalé c’est une façon impérative de travaillé alors NBehave nous demande de passer à la manière déclarative avec des attributs.

       

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private HomeController controller; 
private Story story;

[Story, That, Should("Afficher la page d'accueil avec le message 'Welcome to ASP.NET MVC!'")]
public override void MainSetup()
{
base.MainSetup();

story = new Story("Page d'accueil");
controller = new HomeController();

story.AsA("utilisateur")
.IWant("accéder à la page d'accueil")
.SoThat("je vois le message ‘Welcome to ASP.NET MVC!’");
}
7. Maintenant que nous avons notre Story, définissons un Scenario. Le scénario sera notre premier test unitaire. Le scénario décrit une chose possible qui peut arriver comme par exemple l’affichage de notre page d’accueil. Dans la logique de BDD/TDD d’abord nous devons écrire le test avant le code de production. Ensuite, le test écrit doit échouer afin de s’assurer que notre code que nous allons tester ne marche pas par hasard, etc. Pour la simplicité de cette exemple nous n’allons pas rentrer dans le respect des étapes du TDD et nous allons passer directement au petit test qui s’assurer que notre message est bien affiché sur la page d’accueil

       

1
2
3
4
5
6
7
8
9
[@Specification] 
public void Index()
{
ViewResult result = null;
story.WithScenario("Ma page d'accueil")
.Given("Mon contrôleur est prêt", () => controller.ShouldNotBeNull())
.When("Quand un utilisateur navigue à la vue Index", () => result = controller.Index() as ViewResult)
.Then("On voit le message de bienvenue", () => "Welcome to ASP.NET MVC!".ShouldEqual<string>(result.ViewData["Message"].ToString()));
}

Tout d’abord nous commençons par le WithScenario pour indiquer à l’outil de test avec quel scénario nous allons travailler.

  • Ensuite nous utilisons Given() comme pré condition à notre test. Ici notre contrôleur n’est doit pas être null. Une Action est également passée en paramètre (controller.ShouldNotBeNull()) qui fait ce que nous décrivant “Mon contrôleur est prêt”.
  • When() permet de décrire l’action que nous testons
  • Then() permet de vérifier que tout s’est passé correctement.

Quand on exécute notre test dans NUnit nous voyons que tout se passe bien:

NunitOK

Pour montrer que le test marche j’introduit volontairement une faute dans la dernière ligne de notre test :

1
.Then("On voit le message de bienvenue", () => "Welcome to ASPsss.NET MVC!".ShouldEqual<string>(result.ViewData["Message"].ToString()));

Si on relance NUnit alors le test ne passe plus:

NUnitKO

Nous pouvons ajouter le deuxième test pour l’action About:

1
2
3
4
5
6
7
8
9
[@Specification] 
public void About()
{
ViewResult result = null;
story.WithScenario("Ma page d'accueil")
.Given("Mon contrôleur est prêt", () => controller.ShouldNotBeNull())
.When("Quand un utilisateur navigue à la vue About", () => result = controller.About() as ViewResult)
.Then("La vue est affichée", () => result.ShouldNotBeNull());
}

Conclusion

Comme vous pouvez le constater l’utilisation de NBehave n’ajouter rien de nouveau par rapport à ce qui existe déjà. C’est une autre approche pour écrire de tests unitaires. L’avantage est qu’elle est plus concise que la plus part d’autre frameworks et plus déclarative donc forcément plus facile à comprendre sans analyser forcément le code. Quand vous passez à l’utilisation des attributs pour décrire vos scénario le déclaratif est poussé encore à un autre niveau. Il est plus facile d’implémenter les Use Cases dans vos tests et de conformer votre application à des vrai besoins clients. C’est approche est d’autant plus vraie que si NBehave est appliqué aux tests du la couche métier. Un autre point important concerne ASP.NET MVC qui est facilement testable à l’aide de n’importe quel framework de tests. Dans un prochain article je vais présenter quelques astuces pour tester les application MVC à l’aide de MSTest.

A bientôt.