mercredi 12 septembre 2012

Unit Test et Entity Framework (ou tout autre ORM) : à oublier

Depuis quelques temps j'utilise intensément des ORM (pour Object Relationnal Mapping), des outils qui permettent de gérer facilement le mapping entre les objets métiers et la base de donnée (donc qui nous évite tout le débug des requêtes SQL et du mapping depuis leur résultats).

Lorsque l'on code avec des tests unitaires il faut "mocker" ces accès à la base : un test unitaire n'est censé ne tester qu'une "unité de dévelopement" soit un chemin d'éxecution dans une méthode. Donc si l'on veut par exemple tester sont controller il faut que toutes les classes utilisées par ce dernier soient "mockées" c'est à dire que au lieu d'envoyer l'implémentation concrète des dépendances on envoi une classe qui retourne toujours le même résultat en répondant a l'interface attendue.

Exemple :

CustomerController utilise MyDataContext (classe de Entity Framework permettant d'accéder aux données en base). Je ne peut pas le "unit tester" car MyDataContext est une implémentation concrète (= pas une interface).
Donc deux solutions s'offres à moi :
  1. Faire en sorte que MyDataContext implémente une interface et injecter la dépendance dans le constructeur (cette injction de dépendance peut être faites via Ninject)

    En "Fakant" cette interface :
    Problème : dans mon controlleur le code "_dbContext.Customers.OrderBy(c => c.Name).ToList()" est traduit en SQL par entity framework. Et parfois ces expressions linq peuvnt ne pas pouvoir être traduites (et donc une exception est levée) ce qui fait que moi test unitaire pourrait passer, mais mon code réel non (car le code de test utilise une énumération en mémoire donc tout le linq devrait marcher). Donc cette solutions est une mauvaise solution. 
  2. Enlever la dépendance entre le controlleur et le contexte en encapsulant ce dernier dans un Repository : Le controlleur ignore qu'on utilise un ORM. On appelle cette nouvelle classe un "Repository". Il est possible de trouver de nombreuses implémentations de cette classe : http://www.lmgtfy.com/?q=entity+framework+generic+repository. Mais selon moi cette approche force a créer beaucoup trop de code d'insfrastructure et n'apporte au final que de la complexité dans le dev à cause des trops nombreuse couche d'abastraction comme décrit par le célèbre bloggeur Oren Eini. Tout d'abord elle n'apporte rien vu que DbSet de EF est déjà un repository (et DbContext un UnitOfWork), et elle empèche d'utiliser pleinement toutes les fonctionnalités de EF vu que le controlleur ignore qu'il l'utilise.

Mais du coup quelle solution ?  Je dirais pour les Test Unitaire et Entity Framework : aucune. L'approche qu'il me semble la plus facile a mettre en place est certainement le test d'intégration : on ne teste plus une partie du logiciel mais un processus dans son ensemble par exemple "Je crée une commande est ce que je la vois a la fin avec les bonnes infos". Techniquement il suffit de créer une copie de la base sur un sql express en local. Ensuite de mettre en place une comparaison de schéma avec visual studio (Données -> Comparaison de schéma -> Nouvelle comparaison de schéma) pour synchronsier base de Dev et la base de Test. Puis d'encapsuler les tests dans une transaction que l'on rollback a la fin de chaques test. Le seul inconvéniant étant le temps d'éxecution des tests vu que EF initialize le modèle.



lundi 10 septembre 2012

Remi DDD Part III : Commande

En CQS une commande est un processus qui va modifier l'état global de l'application ( par example la base de donnée) sans retourner de résultats (void). Une commande va par exemple être "Vendre un produit". Vu que j'essai d'appliquer les principes du DDD, ici on aura explicitement "Vendre un produit" dans le code et non pas "Créer commande".
Les entités :
Voici la commande (qui a été crée par l'UI, web ou non) :

On peut remarquer qu'elle implémente l'interface ICommand. Cette interface ne définit aucune méthodes, elle sert juste à "flagger" une classe comme commande.
Le "command hanlder" est la classe qui va éxecuter la commande, il ne peut y en avoir qu'un et un seul par type de commande. Il va simplement insérer les données en base.
Voici l'appel :

Utilisation de Transaction avec Entity Framework 4 et Ninject pour une application Asp.Net MVC 3

Lorsque l'on a une application qui effectue de nombreux aller-retour avec la base de donnée il est important d'encapsuler les mêmes appels d'un processus dans une transaction. Ceci afin d'éviter d'avoir une applicaiton dans un état non prévu.

Exemple : vous insérer la Commande en base, et une erreur .net est levée dans le code qui insère les LigneCommande. Donc en base vous avez une commande sans lignes = état non prévu.

Dans mon projet actuel j'utilise Entity Framework 4, Asp.NetMVC 4 et Ninject pour l'injection de dépendance.

Tout se passe dans la config de ninject, je crée une transaction sql pour chaques requetes http et je rollback cette transaction si une erreur a été levée.

Donc ici on instantie le transactionscope sur OpenTransaction, en s'abonnant a l'evènement d'application Error pour faire un Rollback de la transaction en cas d'erreur.

EDIT : Attention à bien instancier le TransactionScope, il semblerai que la configuration de base lock la base (IsolationLevel.Serializable) et force un timeout de transaction faible (30 sec sachant qu'il y en a déjà un au niveau commande). Je m'en suis rendu compte en tentant de faire un select sur ma base (dans SSMS) en étant sur un point d'arret dans mon appli. Le select a fait un timeout à cause de cet isolation level.

Référence : http://blogs.msdn.com/b/dbrowne/archive/2010/06/03/using-new-transactionscope-considered-harmful.aspx

Il faut donc mieux faire

new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TransactionManager.MaximumTimeout })