dimanche 28 décembre 2008

Tests unitaires et premières lignes de code

Configuration du projet de test

Maintenant que la couche donnée est fonctionnelle, procédons aux premiers tests

Notre solution contient un second projet : le projet de tests unitaires.
1) Définir ce projet comme projet de démarrage et le lancer.

VS test par defaut

Par défaut, la solution effectue deux tests sur un des contrôleurs

2) Copier/coller la chaîne de connexion du modèle EntityFramework depuis le web.config du projet web

3) Dans cette chaîne, remplacer MonBlogMVC par MonBlogMVC.Test pour que nos tests s'effectuent sur une base vierge

<add name="MvcBlogEntities" connectionString="metadata=res://*/Models.MvcBlogModel.csdl|res://*/Models.MvcBlogModel.ssdl|res://*/Models.MvcBlogModel.msl;provider=System.Data.SqlClient;provider connection string="Data Source=LYRE\SQLEXPRESS;Initial Catalog=MonBlogMVC.Test;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />

4) Créer un répertoire 'Models' à la racine de 'McvBlog.Tests'
On placera dans ce répertoire tous les tests unitaires les classes de McvBlog.Models.

Premier test unitaire

5) Créer un premier test unitaire ('ajouter' / 'test unitaire') nommé TestUser
Par défaut il ressemblera à ça :


using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MvcBlog.Tests.Models
{
/// <summary>
/// Summary description for TestUser
/// </summary>
[TestClass]
public class TestUser
{
public TestUser()
{
//
// TODO: Add constructor logic here
//
}

private TestContext testContextInstance;

/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}

#region Additional test attributes
//
// You can use the following additional attributes as you write your tests:
//
// Use ClassInitialize to run code before running the first test in the class
// [ClassInitialize()]
// public static void MyClassInitialize(TestContext testContext) { }
//
// Use ClassCleanup to run code after all tests in a class have run
// [ClassCleanup()]
// public static void MyClassCleanup() { }
//
// Use TestInitialize to run code before running each test
// [TestInitialize()]
// public void MyTestInitialize() { }
//
// Use TestCleanup to run code after each test has run
// [TestCleanup()]
// public void MyTestCleanup() { }
//
#endregion

[TestMethod]
public void TestMethod1()
{
//
// TODO: Add test logic here
//
}
}
}

6) Modifier cette classe. Elle contiendra tous nos tests unitaires de la classe MvcBlog.Models.User :

  • Rajouter un using pour MvcBlog.Models
  • Supprimer le constructeur (inutile)
  • Déplacer le champ et la propriété du TestContext dans une classe de base qui servira à tous nos tests unitaires
  • Supprimer les 'additionnal test attributes', sauf MyClassCleanup qui va nous servir à faire le ménage
  • Ecrire le premier test : la création d'un utilisateur
  • Dans MyClassCleanup, rajouter une routine de suppression de tous les utilisateurs. La base doit être nettoyée après le test.s

Votre code doit maintenant ressembler à ceci :


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

using MvcBlog.Models;

namespace MvcBlog.Tests.Models
{
/// <summary>
/// Tests unitaires pour la classe User
/// </summary>
[TestClass]
public class TestUser
{
#region Initialisation / nettoyage

/// <summary>
/// Après que tous les test soient terminés
/// </summary>
[ClassCleanup()]
public static void MyClassCleanup()
{
using(MvcBlogEntities ctx = new MvcBlogEntities())
{
// supprime tous les utilisateurs
foreach(User obj in ctx.Users)
ctx.DeleteObject(obj);

ctx.SaveChanges();
}
}

#endregion

/// <summary>
/// Test 1 : création d'un utilisateur
/// </summary>
[TestMethod]
public void TestCreation()
{
int count = 0;

using(MvcBlogEntities ctx = new MvcBlogEntities())
{
// créer une nouvelle instance
User user = new User("Mose", "mose@mose.mo", "azerty");

// l'ajouter à nos entités
ctx.AddToUsers(user);
// enregistrer en base
count = ctx.SaveChanges();
}

// si on a bien 1 ligne affectée, c'est réussi
Assert.AreEqual(1, count, "Chargement échoué : trop ou pas assez de ligne affectées");
}
}
}

Notre classe de test de base :


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

namespace MvcBlog.Tests
{
/// <summary>
/// Unit test base class
/// </summary>
public class TestUnitBase
{
#region TestContext management

/// <summary>
/// test context which provides information about and functionality for the current test run.
///</summary>
private TestContext _testContextInstance;
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get { return this._testContextInstance; }
set { this._testContextInstance = value; }
}

#endregion
}
}

Lancez le test : il semble que tout fonctionne.
Néanmoins nous ne sommes pas sûrs que l'utilisateur a réellement été créé.
Pour le vérifier, trois possibilités que je vous invite à tester :

  • Faire un point d'arrêt au début de MyClassCleanup et vérifier directement dans la base de donnée
  • Commenter le code de nettoyage et lancer deux fois le test : les contraintes d'unicité du login et de l'e-mail vont provoquer une exception
  • Rajouter un test de chargement

7) Ajouter le test pour tester le chargement de l'utilisateur créé précédemment
On utilise la syntaxe de LinQ que vous devez déjà connaître puisque c'est un pré-requis à ce tutoriel.


/// <summary>
/// Test 2 : chargement de l'utilisateur
/// </summary>
[TestMethod]
public void TestChargement()
{
using(MvcBlogEntities ctx = new MvcBlogEntities())
{
// on tente un chargement
var queryUsers = from u in ctx.Users
where u.UserName == "Mose"
select u;

// on vérifie qu'on a bien 1 seul et unique résultat
Assert.AreEqual(1, queryUsers.Count(), "Chargement échoué : trop ou pas assez d'utilisateurs retournés");
}
}

Aucune surprise : le test passe très bien
Nous avons donc maintenant :

  • Une couche donnée qui peut aller créer et récupérer des données en base
  • Un projet de test avec ses premiers tests unitaire afin de valider le fonctionnement de cette couche donnée

Vous pouvez dès maintenant écrire d'autres tests unitaires afin de manipuler un peu vos entités.
Nous reviendrons sur ce point ultérieurement, et nous ferons d'autres tests unitaires après avoir enrichi nos modèles.


Important

Comme l'a très exactement fait remarqué un lecteur attentif, on ne peut pas parler ici de 'test unitaire'.

Un test unitaire est un test qui se suffit à lui même, qui n'a pas besoin des autres tests pour réussir, qu'on peut lancer seul.
Or nos tests ici sont liés les uns aux autre : TestChargement nécessite la réussite de TestCreation. Cette approche a été choisie délibérément pour simplifier la mise en place des tests, et parce qu'il est très pénible de faire exclusivement des tests unitaires.

L'unité de nos tests sera donc l'objet métier.

Aucun commentaire:

Enregistrer un commentaire