J'ai un certain nombre de méthodes de logique métier qui stockent et récupèrent (avec filtrage) les objets et les listes d'objets du cache.
Considérer
IList<TObject> AllFromCache() { ... }
TObject FetchById(guid id) { ... }
IList<TObject> FilterByPropertry(int property) { ... }
Fetch..
et Filter..
appellerait AllFromCache
qui remplirait le cache et retournerait s'il n'est pas là et reviendrait simplement de lui s'il l'est.
Je répugne généralement à tester ces unités. Quelles sont les meilleures pratiques pour les tests unitaires contre ce type de structure?
J'ai envisagé de remplir le cache sur TestInitialize et de le supprimer sur TestCleanup mais cela ne me semble pas correct (cela pourrait bien l'être).
la source
Le principe de responsabilité unique est votre meilleur ami ici.
Tout d'abord, déplacez AllFromCache () dans une classe de référentiel et appelez-la GetAll (). Qu'il récupère du cache est un détail d'implémentation du référentiel et ne devrait pas être connu par le code appelant.
Cela rend le test de votre classe de filtrage agréable et facile. Il ne se soucie plus d'où vous l'obtenez.
Ensuite, encapsulez la classe qui obtient les données de la base de données (ou n'importe où) dans un wrapper de mise en cache.
L'AOP est une bonne technique pour cela. C'est l'une des rares choses dans lesquelles il est très bon.
À l'aide d'outils comme PostSharp , vous pouvez le configurer de sorte que toute méthode marquée avec un attribut choisi soit mise en cache. Cependant, si c'est la seule chose que vous mettez en cache, vous n'avez pas besoin d'aller aussi loin que d'avoir un framework AOP. Il suffit d'avoir un référentiel et un wrapper de mise en cache qui utilisent la même interface et l'injectent dans la classe appelante.
par exemple.
Vous voyez comment vous avez supprimé les connaissances d'implémentation du référentiel du ProductManager? Voyez également comment vous avez adhéré au principe de responsabilité unique en ayant une classe qui gère l'extraction des données, une classe qui gère la récupération des données et une classe qui gère la mise en cache?
Vous pouvez maintenant instancier le ProductManager avec l'un de ces référentiels et obtenir la mise en cache ... ou non. Cela est incroyablement utile plus tard lorsque vous obtenez un bug déroutant que vous soupçonnez être le résultat du cache.
(Si vous utilisez un conteneur IOC, c'est encore mieux. Il devrait être évident de savoir comment vous adapter.)
Et, dans vos tests ProductManager
Pas besoin de tester le cache du tout.
Maintenant, la question devient: Dois-je tester ce CachedProductRepository? Je suggère que non. Le cache est assez indéterminé. Le framework fait des choses hors de votre contrôle. Par exemple, en supprimant simplement des éléments lorsqu'ils sont trop pleins, par exemple. Vous allez vous retrouver avec des tests qui échouent une fois dans une lune bleue et vous ne comprendrez jamais vraiment pourquoi.
Et, après avoir apporté les modifications que j'ai suggérées ci-dessus, il n'y a vraiment pas beaucoup de logique à tester là-dedans. Le test vraiment important, la méthode de filtrage, sera là et complètement abstrait du détail de GetAll (). GetAll () juste ... obtient tout. De quelque part.
la source
Votre approche suggérée est ce que je ferais. Compte tenu de votre description, le résultat de la méthode devrait être le même que l'objet soit présent dans le cache ou non: vous devriez toujours obtenir le même résultat. C'est facile à tester en configurant le cache d'une manière particulière avant chaque test. Il y a probablement quelques cas supplémentaires comme si le guid est
null
ou si aucun objet n'a la propriété demandée; ceux-ci peuvent également être testés.En outre, vous pouvez considérer qu'il s'attendait à ce que l'objet soit présent dans le cache après le retour de votre méthode, qu'il soit dans le cache en premier lieu. Ceci est controversé, car certaines personnes (moi y compris) diraient que vous vous souciez de ce que vous récupérez de votre interface, pas de la façon dont vous l'obtenez (c'est-à-dire de vos tests que l'interface fonctionne comme prévu, pas qu'elle a une implémentation spécifique). Si vous le jugez important, vous avez la possibilité de le tester.
la source
En fait, c'est la seule façon correcte de le faire. C'est à cela que servent ces deux fonctions: fixer les conditions préalables et nettoyer. Si les conditions préalables ne sont pas remplies, votre programme peut ne pas fonctionner.
la source
Je travaillais sur certains tests qui utilisent le cache récemment. J'ai créé un wrapper autour de la classe qui fonctionne avec le cache, puis j'ai affirmé que ce wrapper était appelé.
Je l'ai fait principalement parce que la classe existante qui fonctionne avec le cache était statique.
la source
Il semble que vous souhaitiez tester la logique de mise en cache, mais pas la logique de remplissage. Je vous suggère donc de vous moquer de ce que vous n'avez pas besoin de tester - remplir.
Votre
AllFromCache()
méthode se charge de remplir le cache, et cela devrait être délégué à autre chose, comme un fournisseur de valeurs. Donc, votre code ressemblerait àVous pouvez maintenant vous moquer du fournisseur pour le test, pour renvoyer des valeurs prédéfinies. De cette façon, vous pouvez tester votre filtrage et votre récupération réels, et non le chargement d'objets.
la source