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.



Aucun commentaire:

Enregistrer un commentaire