10
votes

Mocking Out Nibernate Queryover avec MOQ

La ligne suivante échoue avec une référence NULL, lors du test: xxx

mon test est comme: xxx

pour être honnête Je rafraîchis dans le noir ici - je suis relativement nouveau à Nhibernate et MOQ, alors je ne suis donc pas sûr de quoi Google pour obtenir les bonnes informations.


0 commentaires

5 Réponses :


1
votes

Je ne pense pas que le code ci-dessus a raison. La Querlerover Afaik est une méthode d'extension sur l'interface d'isession et vous ne pouvez pas se moquer de la méthode d'extensions comme celle-là (au moins pas avec des outils moqueurs classiques tels que MOQ ou Rhinomocks).


1 commentaires

Queryover n'est pas une méthode d'extension. Vous pensez à Query



2
votes

N'essayez pas de se moquer de la neulover. Au lieu de cela, définissez une interface de référentiel (qui utilise la requérante en interne) et se moquez cette interface.


0 commentaires

3
votes

Ce n'est pas une bonne idée. Vous ne devriez pas MOCK Les types que vous ne faites pas propre . Au lieu de cela, vous devriez introduire un référentiel , base de son interface sur le domaine / la langue d'activité et le mettre en œuvre à l'aide de NHibernate. La mise en œuvre peut utiliser ICRITERIA, HQL, QUERYOVER, LINQ, etc. Le point est que cette décision sera encapsulée et cachée du code qui consomme un référentiel.

Vous pouvez écrire un test d'intégration qui va tester une combinaison de votre interface + une base de données réelle orm + réelle ou fausse. S'il vous plaît jeter un coup d'oeil à Ceci et Ceci réponses sur les référentiels et l'accès aux données. Test du code utilisant le référentiel est également super facile, car vous pouvez vous moquer d'une interface de référentiel.

Quels sont les avantages de cette approche autre que la qualification? Ils sont quelque peu liés les uns aux autres:

  • séparation des préoccupations. Problèmes d'accès aux données résolus dans la couche d'accès aux données (mise en œuvre du référentiel).
  • Couplage lâche. Le reste du système n'est pas couplé à l'outil d'accès à l'accès aux données. Vous avez un potentiel de mise en œuvre du référentiel de commutation de NHibernate à SQL RAW, Azure, Service Web. Même si vous n'avez jamais besoin de basculer, la superposition est mieux appliquée si vous concevez «comme si» vous devez basculer.
  • lisibilité. Les objets de domaine, y compris la définition de l'interface de référentiel sont basés sur la langue des affaires / domaines.

3 commentaires

Pour toute application mature, la stratégie d'accès aux données n'est pas décidée sur une caprice et certainement pas changée chaque jour. Cacher Nhibernate (ou EF pour cette affaire) derrière un référentiel signifie que vous perdez les avantages NHibernate fournit (extraction explicite paresseuse / désireuse, etc.) Sauf si vous déclarez toutes les sessions possibles.Querauver / iCriteria permutation dans l'interface du référentiel. Voir Le faux mythe de l'encapsulation Accès aux données dans le DAL - AyENDE @ Rahien


@ Janv8000 Vous avez toujours NHibernate sous le capot avec tous ses avantages. Je préfère traiter NHibernate comme un détail de mise en œuvre et mettre l'accent sur la logique commerciale et en vous assurant que mon modèle de domaine utilise la même langue que les mêmes besoins en matière d'entreprise. Vous n'avez pas besoin de déclarer toutes les permutations - vous créez une méthode significative comme 'ordres.delinquant ()', etc. Vous pouvez également utiliser un modèle de spécification dans de rares cas où vous ne pouvez pas prédire quelle combinaison d'attributs seront interrogés sur (quelque chose comme la fonctionnalité «Recherche avancée»).


@ Janv8000 concernant l'article AyENDEE. Je conviens que supposer que vous pouvez facilement changer d'ormes est irréaliste. Mais vous pouvez toujours concevoir as_if vous voulez pouvoir, et il présente d'énormes avantages sur la qualité globale de la conception (lisibilité, testabilité, couplage en vrac). Oui, la commutation ORM est dure et la plupart du temps irréaliste, mais ce n'est pas une excuse pour créer une boule de boue étroitement couplée et intense.



3
votes

J'ai utilisé plusieurs approches dans le passé. Une solution est que d'autres ont suggéré de créer une classe de référentiel que vous pouvez moquer / talon qui encapsule vos requêtes. Un problème avec c'est que ce n'est pas très flexible et que vous enverrez une procédure stockée comme une solution, à l'exception de celui-ci est en code plutôt que la base de données.

Une solution récente que j'ai essayée consiste à créer un talon de queressover que je fournis lorsque Je saisis la méthode de Querlerover. Je peux ensuite fournir une liste d'éléments à retourner. N'oubliez pas que si vous utilisez cette approche, vous devez non seulement écrire des tests d'unité, mais un test d'intégration, qui vérifiera si la requête fonctionne ou non. P>

public class QueryOverStub<TRoot, TSub> : IQueryOver<TRoot, TSub>
{
    private readonly TRoot _singleOrDefault;
    private readonly IList<TRoot> _list;
    private readonly ICriteria _root = MockRepository.GenerateStub<ICriteria>();

    public QueryOverStub(IList<TRoot> list)
    {
        _list = list;
    }

    public QueryOverStub(TRoot singleOrDefault)
    {
        _singleOrDefault = singleOrDefault;
    }

    public ICriteria UnderlyingCriteria
    {
        get { return _root; }
    }

    public ICriteria RootCriteria
    {
        get { return _root; }
    }

    public IList<TRoot> List()
    {
        return _list;
    }

    public IList<U> List<U>()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TRoot> ToRowCountQuery()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TRoot> ToRowCountInt64Query()
    {
        throw new NotImplementedException();
    }

    public int RowCount()
    {
        return _list.Count;
    }

    public long RowCountInt64()
    {
        throw new NotImplementedException();
    }

    public TRoot SingleOrDefault()
    {
        return _singleOrDefault;
    }

    public U SingleOrDefault<U>()
    {
        throw new NotImplementedException();
    }

    public IEnumerable<TRoot> Future()
    {
        return _list;
    }

    public IEnumerable<U> Future<U>()
    {
        throw new NotImplementedException();
    }

    public IFutureValue<TRoot> FutureValue()
    {
        throw new NotImplementedException();
    }

    public IFutureValue<U> FutureValue<U>()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TRoot> Clone()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot> ClearOrders()
    {
        return this;
    }

    public IQueryOver<TRoot> Skip(int firstResult)
    {
        return this;
    }

    public IQueryOver<TRoot> Take(int maxResults)
    {
        return this;
    }

    public IQueryOver<TRoot> Cacheable()
    {
        return this;
    }

    public IQueryOver<TRoot> CacheMode(CacheMode cacheMode)
    {
        return this;
    }

    public IQueryOver<TRoot> CacheRegion(string cacheRegion)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> And(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> And(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> And(ICriterion expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> AndNot(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> AndNot(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> AndRestrictionOn(Expression<Func<TSub, object>> expression)
    {
        throw new NotImplementedException();
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> AndRestrictionOn(Expression<Func<object>> expression)
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TSub> Where(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> Where(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> Where(ICriterion expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> WhereNot(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> WhereNot(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> WhereRestrictionOn(Expression<Func<TSub, object>> expression)
    {
        return new IQueryOverRestrictionBuilder<TRoot, TSub>(this, "prop");
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> WhereRestrictionOn(Expression<Func<object>> expression)
    {
        return new IQueryOverRestrictionBuilder<TRoot, TSub>(this, "prop");
    }

    public IQueryOver<TRoot, TSub> Select(params Expression<Func<TRoot, object>>[] projections)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> Select(params IProjection[] projections)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> SelectList(Func<QueryOverProjectionBuilder<TRoot>, QueryOverProjectionBuilder<TRoot>> list)
    {
        return this;
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderBy(Expression<Func<TSub, object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderBy(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, false);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderBy(IProjection projection)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, projection);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderByAlias(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, true);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenBy(Expression<Func<TSub, object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenBy(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, false);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenBy(IProjection projection)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, projection);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenByAlias(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, true);
    }

    public IQueryOver<TRoot, TSub> TransformUsing(IResultTransformer resultTransformer)
    {
        return this;
    }

    public IQueryOverFetchBuilder<TRoot, TSub> Fetch(Expression<Func<TRoot, object>> path)
    {
        return new IQueryOverFetchBuilder<TRoot, TSub>(this, path);
    }

    public IQueryOverLockBuilder<TRoot, TSub> Lock()
    {
        throw new NotImplementedException();
    }

    public IQueryOverLockBuilder<TRoot, TSub> Lock(Expression<Func<object>> alias)
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(_list);
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<TSub, object>> path, Expression<Func<object>> alias)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<object>> path, Expression<Func<object>> alias)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<TSub, object>> path, Expression<Func<object>> alias, JoinType joinType)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<object>> path, Expression<Func<object>> alias, JoinType joinType)
    {
        return this;
    }

    public IQueryOverSubqueryBuilder<TRoot, TSub> WithSubquery
    {
        get { return new IQueryOverSubqueryBuilder<TRoot, TSub>(this); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Inner
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.InnerJoin); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Left
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.LeftOuterJoin); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Right
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.RightOuterJoin); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Full
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.FullJoin); }
    }
}


9 commentaires

Un référentiel moqueur et testable qui encapsule l'accès des données est comme la procédure stockée? Vous préférez recommander de répandre une queryover partout? Cela détruirait la «couche de domaine» et diffusera ses responsabilités entre l'interface utilisateur et la couche d'accès aux données. Ce serait certainement super flexible: en.wikipedia.org/wiki/big_ball_of_mud


@Dimititrie, plusieurs points. Oui Un référentiel où vous gardez toutes vos questions, c'est exactement comme ayant un ensemble de Procs stockés. Que se passe-t-il s'il doit interroger la même entité, mais en utilisant 2 paramètres? Ou peut-être qu'il veut juste une projection? Comment utiliser des requêtes si nécessaire 'Détruire la couche de domaine'? Vos entités de domaine contiennent toujours la logique commerciale. Outre NHibernate est déjà une abstraction de l'accès aux données. Pourquoi auriez-vous envie de vous en résumer? Cela facilite les tests, mais refactore plus fort, imo.


@Dimitry consultez également ces articles par Oren ayende.com/blog/4784/... ayende.com/blog/4783/...


Proc. sont mauvais parce qu'ils sont 1) écrit dans une langue procédurale archaïque 2) externalisez la logique commerciale 3) Très difficile à tester 4) Réduire l'évolutivité. Aucun de ceux-ci ne s'applique à un référentiel correctement conçu. En ce qui concerne votre 'ce qui se passe si?' - Que se passe-t-il, c'est que vous modifiez le code en fonction des besoins de l'entreprise et de l'écriture des tests. Votre approche est-elle une certaine immunité à cela? Accès aux données correctement abstrait favorisent: Séparation des préoccupations, couplage lâche, testabilité, lisibilité et plus de code orienté sur le domaine / entreprise. Si vous n'aimez pas l'abstraire, pourquoi ne pas utiliser SQL directement? Ce serait tellement flexible.


Concernant les articles par Oren. Je pense qu'il l'approche de la perspective purement mécanique des «données de données». Les applications modernes sont beaucoup plus que «Comment pouvons-nous obtenir les données». Au moment où vous répandez des types d'accès de données les plus récents et les plus grands sur votre base de code, le code n'est plus orienté sur le domaine et expressif et ne fait que bouquet de boules de poils, procédurales, inchangées et non conditionnelles. Ce qui n'est pas un problème si était un consultant et son travail de quelqu'un d'autre pour le soutenir.


En plus des blogs d'Oren, il pourrait être intéressant de regarder: Amazon.com / Domaine-Driven-Design-Tacking-complexité-Software / ... , Amazon.com/patterns-enterprise-Application-Architecture-Architecture-mart dans / ... , Amazon.com/Grave-Object-Oriented-software-Soft-Soft-Soft-STS/DP/ ... , Amazon.com/DePendency-Injection-NET-Mark-Seemann/DP/19351825 01 / ... < / a>


L'approche de l'utilisation de NHibernate directement est à l'abri d'avoir à créer un tas de méthodes de référentiel personnalisées qui ne sont utilisées que dans 1 point. Vous pouvez toujours tester à ce sujet avec l'approche que j'ai décrite dans la réponse (talon personnalisé pour les tests d'unité, l'accès complet à la base de données pour les tests d'intégration) Je ne comprends toujours pas comment n'utiliser pas un référentiel pour interrogation de données signifie que nous ne pratiquons pas DDD. Rien ne vous empêche de créer des entités de domaine riches. Pouvez-vous ne pas pratiquer DDD dans ROR simplement parce qu'il est courant d'utiliser Activerecord directement pour interroger des données?


DDD est un ensemble de lignes directrices et non une formule stricte. Vous pouvez probablement prétendre qu'il peut être mis en œuvre à l'aide de l'assembleur X86 et je ne veux vraiment pas discuter avec vous. Les implémentations saines DDD qui traitent des objets de longue durée utilisent le motif de référentiel. L'ensemble du point de ce modèle consiste à encapsuler des technicosités d'accès aux données derrière l'interface axée sur le domaine. Pas axé sur les données. Quand je vois Queryover, je vois Accès aux données . Quand je vois DelinquentordersPec, je vois les exigences de l'entreprise.


Activerecord est orthogonal au modèle de domaine, vous utilisez soit AR ou DM. Vous pouvez bien sûr implémenter le modèle de domaine au-dessus de Aciverecord mais quel est le point? À mon avis, une approche que vous promouvez est équivalente à la diffusion de SQL directement sur la base du code. Le seul avantage est qu'il offre primitif type sécurité. À mon avis, il favorise le couplage serré, décourage le test de l'unité et n'a aucune séparation des préoccupations. C'est DDD si DDD représente le développement axé sur les données.



1
votes

Dernièrement, je déplace le code qui appelle .Querlyover () à une méthode virtuelle protégée à la place et à construire mon propre testtableXYZ qui hérite de XYZ et remplace la méthode et renvoie une liste vide ou quelque chose. De cette façon, je n'ai pas besoin d'un référentiel juste pour tester.


0 commentaires