A bord de Salamanca

Aller au contenu | Aller au menu | Aller à la recherche

Contrôle des papiers...

En quittant notre dernière escale, nos hôtes nous ont demander de présenter des informations validées sur notre équipée. Heureusement, nous avons dans Salamanca une bibliothèque qui permet de vérifier aisément la saisie de données : Data Rules !

Pour rendre un système d'information utile et efficace, il ne suffit pas de l'alimenter en données, il faut aussi en assurer la cohérence.

La première exigence qui en découle est de valider les informations fournies si possible dés leur saisie et avant leur sauvegarde dans la base de données.

Dans le modèle entité dessiné avec Salamanca, on peut associer à chaque attribut des règles de validation prédéfinies en fonction de spécifications métier telles que :

  • valeur par défaut ; par exemple, le statut du projet est "Nouveau" par défaut
  • autorisation de la valeur null ; par exemple, un projet doit avoir un nom
  • longueur maximum ; par exemple, le nom du projet ne dépasse pas 32 caractères
Ces propriétés sur les attributs d'une entité constituent le premier niveau de règles métier. Elles sont implémentées non seulement dans la base de données sous forme de contraintes, mais aussi dans la couche objet sous forme d'attributs (au sens .NET) liés aux propriétés de l'objet. Par exemple :
[NotNullRule]
[StringLengthRule(32)]
public string Nom
{
...
}

Profitons d'avoir configuré notre projet de tests pour valider que ces règles métier sont vérifiées lors de la sauvegarde d'un projet.

Pour faciliter l'exécution de notre série de tests, nous initialisons chacun avec des données valides pour une instance de Projet défini pour la classe de test :

[TestInitialize()]
public void MyTestInitialize()
{
Client c = new Client(DataMappers);
c.Nom = "Jean Dupond";
Projet = new Projet(c, DataMappers);
Projet.Nom = "Mon projet";
}

Pour tester que le nom saisi dépasse la longueur maximale, nous ajoutons un test avec "un nom de projet vraiment trop long" et nous vérifions que

  • la méthode Validate(string propertyName) d'un objet DomainEntity renvoit FAUX
  • qu'une BrokenRulesException est levée lorsqu'on sauvegarde une instance non valide
[TestMethod]
[ExpectedException(typeof(BrokenRulesException))]
public void CannotValidateNomNull()
{
    Assert.IsTrue(Projet.Validate());
    Projet.Nom = null;
    Assert.IsFalse(Projet.Validate("Nom"));
    Projet.Save();
}

Le code complet de notre classe se résume à la validation d'une série de 3 tests :

  • On doit pouvoir sauvegardé une instance de Projet avec un nom valide
  • On ne doit pas pouvoir sauvegardé une instance de Projet avec un nom vide (NULL)
  • On ne doit pas pouvoir sauvegardé une instance de Projet avec un nom trop long (> 32 caractères)
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Salamanca.DataRules;
using NourY.Eproject.Model;

namespace NourY.Eproject.Tests.Model
{
    [TestClass]
    public class ProjetTest : UnitTestBase, IProjetContainer
    {
        [TestMethod]
        [DeploymentItem("..\\eProject_ABord_Tests.mdf")]
        public void CanSaveAndValidateProjet()
        {
            Assert.IsTrue(Projet.Validate());
            Projet.Save();
        }

        [TestMethod]
        [ExpectedException(typeof(BrokenRulesException))]
        public void CannotValidateNomNull()
        {
            Assert.IsTrue(Projet.Validate());
            Projet.Nom = null;
            Assert.IsFalse(Projet.Validate("Nom"));
            Projet.Save();
        }

        [TestMethod]
        [ExpectedException(typeof(BrokenRulesException))]
        public void CannotValidateNomTooLong()
        {
            Assert.IsTrue(Projet.Validate());
            Projet.Nom = "Un nom de projet vraiment trop long";
            Assert.IsFalse(Projet.Validate("Nom"));
            Projet.Save();
        }

        [TestInitialize()]
        public void MyTestInitialize()
        {
            Client c = new Client(DataMappers);
            c.Nom = "Jean Dupond";
            Projet = new Projet(c, DataMappers);
            Projet.Nom = "Mon projet";
        }

        public Projet Projet
        {
            get { return _Projet; }
            set { _Projet = value; }
        }

        private Projet _Projet;
    }
}

Les règles de validation sur les attributs des entités s'avère donc non seulement aisées à mettre en place mais également implémentées de manière concise dans la couche objet. Pour valider des champs avec des règles avancées tels qu'une adresse email, un numéro de téléphone ou un code postal, nous pouvons exploité la puissance des expressions régulières à travers des règles de type RegexRule.

Cette exploration "forcée" de la bibliothèque DataRule entre deux escales nous ouvre déjà un horizon bien dégagé du côté de la consolidation des données de nos applications métier. A suivre avec leur utilisation dans le cadre d'activités métier...

Escale #3 : Tests unitaires

Nous avons maintenant une base de données à notre disposition. Mais ne perdons pas de vue le but de notre expédition, ou plutôt l'enchainement de buts : apprendre à utiliser Salamanca afin, in fine, de mettre en place un processus métier qui produit de la valeur.

Mais comme dans la fable du lièvre et de la tortue, nous prenons le temps de mettre en place des bonnes pratiques de développement en cours de route afin d'assurer à la fois la qualité de notre produit et la facilité de sa maintenance.

L'escale d'aujourd'hui s'attarde sur la mise en place des outils de contrôle de la qualité : les Tests.

Contexte

Même si nous parlons de tests unitaires, notre intention est plutôt de s'assurer que le code produit est conforme au résultat attendu, par exemple que les règles métier définies sont bien respectées. Dans la terminologie du test informatique, on parle alors de tests fonctionnels (de recette ou encore d'acceptation) par opposition aux tests techniques (unitaires, d'intégration, système, performance, ...).

Mais dans notre cas, nous nous appuyons sur le framework de test de Visual Studio. C'est pourquoi nous avons créé le projet Eproject.Tests lors de la mise en place de la solution (cf L'application Salamanca).

Configuration

Comme l'objet de notre application est de manipuler des données, nous avons besoin d'une base de données dédiée pour les tests qui puisse être recréée à chaque exécution d'un jeu de tests. Pour cela, nous procédons de la même manière que pour la base de données de développement, avec une étape supplémentaire :

  • création d'une base de données SQL Server 2008 Express
  • copie du fichier de base de données dans le projet de tests

Cette procédure peut se traduire sous forme de la commande batch CreateTests.cmd, placée dans le répertoire Create Scripts du projet Eproject.Sql :

SET CONNECT_STRING=.\SQLEXPRESS 
SET BASE_NAME=eProject_ABord_Tests
SET TestsDir=%CD%\..\..\Eproject.Tests

sqlcmd.exe -E -X -S %CONNECT_STRING% -v DatabaseName=%BASE_NAME% -i Create.sql

xcopy "C:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\%BASE_NAME%.mdf" %TestsDir% /y
xcopy "C:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\%BASE_NAME%_log.ldf" %TestsDir% /y

Une fois le fichier de base de données copié, nous l'ajoutons dans notre projet ("Afficher tous les fichiers", clic droit sur le fichier, puis "Inclure dans le projet").

La configuration de sa chaîne de connexion est facilité par l'utilisation du Data Application Block du framework Microsoft Enterprise Library (cf Pré-requis). Pour cela, nous utilisons :

  • un fichier de configuration app.config ajouté dans notre projet
  • l'utilitaire Enterprise Library Configuration

Enterprise Library Configuration

On obtient ainsi le code de configuration suivant :

<configuration> 
  <configSections>
    <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <dataConfiguration defaultDatabase="eProject_ABord_Tests" />
  <connectionStrings>
    <add name="eProject_ABord_Tests" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|eProject_ABord_Tests.mdf;User Instance=true"
      providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

Codage

Pour pouvoir coder des tests qui manipulent des instances des entités métier que nous avons modélisé, nous aurons besoin d'une collection de DataMapper, dont le rôle (tel que je l'ai assimilé) est de servir de point d'ancrage de la couche de persistence, à savoir la gestion du cache et des échanges entre les instances d'objets en mémoire et leur structure de stockage en base de données (cf QuickStart : the Domain Model).

L'implémentation choisie est d'avoir une classe de base dont tous les tests hériterons. C'est une classe publique contenant une propriété de type DataMapperCollection avec un getter sous forme de singleton tirant parti des facilités offertes par les bibliothèques Enterprise Library et DataAccess pour créer une connexion avec la base de données (configurée par défaut dans app.config) ainsi qu'un mode de cache des données (dans notre cas, elles ne seront pas conservées en mémoire).

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Salamanca.DataAccess;
using Salamanca.DataAccess.Collections;
using NourY.Eproject.Model.Mappers.EnterpriseLibrary;

namespace NourY.Eproject.Tests
{
public class UnitTestBase
{
public UnitTestBase()
{
}

protected DataMapperCollection DataMappers
{
get
{
if (_DataMappers == null)
_DataMappers = Mappers.CreateDataMapperCollection(
DatabaseFactory.CreateDatabase(),
new NoCacheManager());
return _DataMappers;
}
}

private DataMapperCollection _DataMappers;
}

References

Pour pouvoir compiler notre projet, nous avons ajouté les références aux bibliothèques contenant les types utilisés dans notre classe de base :

  • DatabaseFactory dans EnterpriseLibrary.Data
  • NoCacheManager et DataMapperCollection dans DataAccess
  • Mappers dans Eproject.Model

 

Voici venu le temps de tester notre configuration à travers un exemple simple : je veux pouvoir sauvegarder le projet nommé "Projet Test" du client nommé "Client Test".

Nous ajoutons le test unitaire CrudProjetTest qui hérite de la classe UnitTestBase. Afin d'avoir une base de données "propre" à chaque exécution, nous l'associons à notre classe de test à travers l'attribut DeploymentItem.

    [TestClass]
[DeploymentItem("..\\eProject_ABord_Tests.mdf")]
public class CrudProjetTest : UnitTestBase
{
[TestMethod]
public void CanSaveProjet()
{
Client c = new Client(DataMappers);
c.Nom = "Projet Test";
Projet p = new Projet(c, DataMappers);
p.Nom = "Client Test";

Assert.IsTrue(p.IsModified);
p.Save();
Assert.IsFalse(p.IsModified);
}
}

Nous vous invitons à tester cette configuration dont le réel intérêt est à terme de pouvoir valider que l'implémentation des activités métier de notre application s'exécute avec succès et respectent les règles métier définis. Mais avant cela, nous verrons évidemment comment concevoir et coder les activités métier, concept essentiel de Salamanca...