Si vous voulez utiliser TDD (ou toute autre approche de test avec une couverture de test élevée) et EF ensemble, vous devez écrire des tests d'intégration ou de bout en bout. Le problème ici est que toute approche avec mocking soit le contexte ou le référentiel crée juste un test qui peut tester votre logique de couche supérieure (qui utilise ces mocks) mais pas votre application.
Un exemple simple :
Définissons le référentiel générique :
public interface IGenericRepository<TEntity>
{
IQueryable<TEntity> GetQuery();
...
}
Et écrivons une méthode de travail :
public IEnumerable<MyEntity> DoSomethingImportant()
{
var data = MyEntityRepo.GetQuery().Select((e, i) => e);
...
}
Maintenant, si vous simulez le référentiel, vous utiliserez Linq-To-Objects et vous aurez un test vert, mais si vous exécutez l'application avec Linq-To-Entities, vous obtiendrez une exception car la surcharge de sélection avec des index n'est pas supportée par L2E.
Il s'agissait d'un exemple simple, mais la même chose peut se produire avec l'utilisation de méthodes dans les requêtes et d'autres erreurs courantes. De plus, cela affecte également les méthodes telles que Add, Update, Delete habituellement exposées dans le référentiel. Si vous n'écrivez pas un objet fantaisie qui simule exactement le comportement du contexte EF et l'intégrité référentielle, vous ne pourrez pas tester votre implémentation.
Une autre partie de l'histoire concerne les problèmes de chargement paresseux qui peuvent aussi difficilement être détectés avec des tests unitaires contre des mocks.
Pour cette raison, vous devriez également introduire des tests d'intégration ou des tests de bout en bout qui fonctionneront sur une base de données réelle en utilisant un contexte EF réel et L2E. En fait, l'utilisation de tests de bout en bout est nécessaire pour utiliser correctement le TDD. Pour écrire des tests de bout en bout en ASP.NET MVC, vous pouvez WatiN et peut-être aussi SpecFlow pour BDD mais cela ajoutera beaucoup de travail mais vous aurez votre application vraiment testée. Si vous voulez en savoir plus sur le TDD, je vous recommande ce livre (le seul inconvénient est que les exemples sont en Java).
Les tests d'intégration ont un sens si vous n'utilisez pas de référentiel générique et que vous cachez vos requêtes dans une classe qui n'exposera pas les données. IQueryable
mais renvoie directement les données.
Exemple :
public interface IMyEntityRepository
{
MyEntity GetById(int id);
MyEntity GetByName(string name);
}
Maintenant, vous pouvez simplement écrire un test d'intégration pour tester l'implémentation de ce référentiel car les requêtes sont cachées dans cette classe et ne sont pas exposées aux couches supérieures. Mais ce type de référentiel est en quelque sorte considéré comme une ancienne implémentation utilisée avec des procédures stockées. Vous perdrez beaucoup de fonctionnalités ORM avec cette implémentation ou vous devrez faire beaucoup de travail additionnel - par exemple ajouter schéma de spécifications pour pouvoir définir une requête dans la couche supérieure.
Dans ASP.NET MVC, vous pouvez partiellement remplacer les tests de bout en bout par des tests d'intégration au niveau du contrôleur.
Modifier en fonction du commentaire :
Je ne dis pas que vous avez besoin de tests unitaires, de tests d'intégration et de tests de bout en bout. Je dis que faire des applications testées demande beaucoup plus d'efforts. La quantité et les types de tests nécessaires dépendent de la complexité de votre application, de l'avenir prévu de l'application, de vos compétences et de celles des autres membres de l'équipe.
Les petits projets simples peuvent être créés sans aucun test (ok, ce n'est pas une bonne idée mais nous l'avons tous fait et à la fin cela a fonctionné) mais une fois qu'un projet a franchi un certain seuil, vous pouvez constater que l'introduction de nouvelles fonctionnalités ou la maintenance du projet est très difficile parce que vous n'êtes jamais sûr de ne pas casser quelque chose qui fonctionnait déjà - c'est ce qu'on appelle la régression. La meilleure défense contre la régression est un bon ensemble de tests automatisés.
- Les tests unitaires vous aident à tester la méthode. Ces tests doivent idéalement couvrir tous les chemins d'exécution de la méthode. Ces tests doivent être très courts et faciles à écrire - la partie la plus compliquée peut être la mise en place de dépendances (mocks, faktes, stubs).
- Les tests d'intégration vous aident à tester la fonctionnalité à travers plusieurs couches et généralement à travers plusieurs processus (application, base de données). Vous n'avez pas besoin d'en avoir pour tout, c'est plutôt une question d'expérience pour choisir où ils sont utiles.
- Les tests de bout en bout sont quelque chose comme la validation du cas d'utilisation / de l'histoire de l'utilisateur / de la fonctionnalité. Ils doivent couvrir l'ensemble du flux des exigences.
Il n'est pas nécessaire de tester une fonctionnalité plusieurs fois - si vous savez que la fonctionnalité est testée dans le test de bout en bout, vous n'avez pas besoin d'écrire un test d'intégration pour le même code. De même, si vous savez que la méthode n'a qu'un seul chemin d'exécution qui est couvert par le test d'intégration, vous n'avez pas besoin d'écrire un test unitaire pour elle. Cela fonctionne beaucoup mieux avec l'approche TDD où l'on commence par un gros test (de bout en bout ou d'intégration) et on va plus loin dans les tests unitaires.
En fonction de votre approche du développement, vous n'êtes pas obligé de commencer avec plusieurs types de tests dès le début, mais vous pouvez les introduire plus tard, lorsque votre application deviendra plus complexe. L'exception est TDD/BDD où vous devriez commencer à utiliser au moins des tests de bout en bout et des tests unitaires avant même d'écrire une seule ligne de code.
Vous posez donc la mauvaise question. La question n'est pas de savoir ce qui est plus simple. La question est de savoir ce qui vous aidera à la fin et quelle complexité convient à votre application. Si vous voulez avoir une application et une logique d'entreprise facilement testées en unité, vous devez envelopper le code EF dans d'autres classes qui peuvent être simulées. Mais en même temps, vous devez introduire d'autres types de tests pour vous assurer que le code EF fonctionne.
Je ne peux pas vous dire quelle approche conviendra à votre environnement / projet / équipe / etc. Mais je peux vous donner des exemples tirés de mes projets passés :
J'ai travaillé sur ce projet pendant environ 5-6 mois avec deux collègues. Le projet était basé sur ASP.NET MVC 2 + jQuery + EFv4 et il a été développé de manière incrémentale et itérative. Il comportait une logique commerciale complexe et des requêtes de base de données compliquées. Nous avons commencé par des référentiels génériques et une couverture de code élevée avec des tests unitaires + des tests d'intégration pour valider le mappage (tests simples pour l'insertion, la suppression, la mise à jour et la sélection d'entités). Après quelques mois, nous avons constaté que notre approche ne fonctionnait pas. Nous avions plus de 1.200 tests unitaires, une couverture de code d'environ 60% (ce qui n'est pas très bon) et beaucoup de problèmes de régression. Tout changement dans le modèle EF pouvait introduire des problèmes inattendus dans des parties qui n'avaient pas été touchées pendant plusieurs semaines. Nous avons découvert qu'il nous manquait des tests d'intégration ou des tests de bout en bout pour la logique de notre application. La même conclusion a été faite par une équipe parallèle travaillant sur un autre projet et l'utilisation de tests d'intégration a été considérée comme une recommandation pour les nouveaux projets.