【问题标题】:Mocking out nHibernate QueryOver with Moq使用 Moq 模拟 nHibernate QueryOver
【发布时间】:2011-11-22 17:49:41
【问题描述】:

测试时,以下行因空引用而失败:

var awards = _session.QueryOver<Body>().Where(x => x.BusinessId == (int)business).List();

我的测试是这样的:

var mockQueryOver = new Mock<IQueryOver<Body, Body>>();
mockQueryOver.Setup(q => q.List()).Returns(new List<Body> {_awardingBody});
_mockSession.Setup(c => c.QueryOver<Body>()).Returns((mockQueryOver.Object));
_mockCommandRunner = new Mock<ICommandRunner>();
_generator = new CertificateGeneratorForOpenSSLCommandLine(_mockSession.Object, _mockCommandRunner.Object, _mockDirectory.Object, _mockFile.Object, _mockConfig.Object); 

老实说,我在这里摸不着头脑——我对 nHibernate 和 Moq 还比较陌生,所以我不太确定用什么谷歌来获取正确的信息。

【问题讨论】:

    标签: c# unit-testing nhibernate moq queryover


    【解决方案1】:

    我过去使用过几种方法。正如其他人所建议的那样,一种方法是创建一个 Repository 类,您可以模拟/存根封装您的查询。这样做的一个问题是它不是很灵活,你最终会得到一个类似存储过程的解决方案,除了这个是在代码中而不是在数据库中。

    我最近尝试的一个解决方案是创建一个 QueryOver 存根,我在存根 QueryOver 方法时提供该存根。然后,我可以提供应退回的物品清单。请记住,如果您使用这种方法,您不仅应该编写单元测试,还应该编写集成测试,它将测试查询是否真正有效。

    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); }
        }
    }
    

    【讨论】:

    • 封装数据访问的可模拟和可测试的存储库就像存储过程?您宁愿建议将 QueryOver 传播到各处?这将破坏“域层”,并将其职责分散到 UI 和数据访问层。那肯定会超级灵活:en.wikipedia.org/wiki/Big_ball_of_mud
    • @Dimitry,几点。是的,保存所有查询的存储库就像拥有一组存储过程一样。如果他需要查询同一个实体,但使用 2 个参数,会发生什么?或者也许他只是想要一个投影?如何根据需要使用查询“破坏域层”?您的域实体仍然包含业务逻辑。此外,NHibernate 已经是数据访问的抽象。为什么你觉得你需要进一步抽象它?它使测试更容易,但重构更难,imo。
    • @Dimitry 还可以查看 Oren ayende.com/blog/4784/… ayende.com/blog/4783/… 的这些文章
    • 存储过程。不好,因为它们是 1) 用过时的程序语言编写的 2) 外部化业务逻辑 3) 很难测试 4) 降低可扩展性。这些都不适用于正确设计的存储库。关于你的“如果会发生什么?” - 发生的事情是您根据业务需求更改代码并编写测试。您的方法是否对此免疫?适当抽象的数据访问促进:关注点分离、松散耦合、可测试性、可读性和更多面向领域/业务的代码。如果你不喜欢抽象它为什么不直接使用 SQL 呢?它会非常灵活。
    • 关于 Oren 的文章。我认为他是从纯粹机械的“数据访问”的角度来处理它的。现代应用程序不仅仅是“我们如何获取数据”。当你在你的代码库中传播最新最好的数据访问类型时,代码不再是面向领域和表达能力的,而是一堆紧密耦合的、程序化的、不可更改的、不可测试的毛球。如果是顾问和其他人的工作来支持它,这不是问题。
    【解决方案2】:

    这不是一个好主意。你不应该mock the types you don't own。相反,您应该引入Repository,将其接口基于域/业务语言并使用 NHibernate 实现它。实现可以使用 ICriteria、HQL、QueryOver、Linq 等。关键是这个决定将被封装并隐藏在使用存储库的代码中。

    您可以编写一个集成测试来测试您的界面 + 真实 ORM + 真实或假数据库的组合。请查看thisthis 关于测试存储库和数据访问的答案。测试使用 Repository 的代码也非常容易,因为您可以模拟 Repository 接口。

    除了可测试性之外,这种方法还有哪些优点?它们之间有些相关:

    • 关注点分离。在数据访问层(存储库实现)中解决了数据访问问题。
    • 松耦合。系统的其余部分不与当前的数据访问工具耦合。您有可能将存储库实现从 NHibernate 切换到原始 sql、azure、Web 服务。即使您从不需要切换,如果您设计“好像”您需要切换,分层也会得到更好的执行。
    • 可读性。领域对象(包括存储库接口定义)基于业务/领域语言。

    【讨论】:

    • 对于任何成熟的应用程序,数据访问策略都不是一时心血来潮决定的,当然也不会每天都在改变。在存储库后面隐藏 NHibernate(或 EF)意味着您失去了 NHibernate 提供的好处(显式延迟/急切获取等),除非您在存储库接口中声明所有可能的 session.QueryOver/ICriteria 排列。见The false myth of encapsulating data access in the DAL - Ayende @ Rahien
    • @janv8000 您仍然拥有 nhibernate 及其所有优点。我更喜欢将 nhibernate 视为实现细节,并专注于业务逻辑并确保我的域模型使用与业务需求相同的语言。您不需要声明每个排列 - 您可以创建一个有意义的方法,例如 'orders.Delinquent()' 等。您还可以在极少数情况下使用规范模式,在这种情况下您无法预测将查询哪些属性组合(一些像“高级搜索”功能)。
    • @janv8000 关于 Ayende 的文章。我同意假设您可以轻松切换 ORM 是不现实的。但是您仍然可以设计您想要的 as_if,并且它对设计的整体质量(可读性、可测试性、松散耦合)有巨大的好处。是的,切换 ORM 很困难,而且在大多数情况下是不现实的,但这不是创建紧密耦合且无法测试的泥球的借口。
    【解决方案3】:

    不要试图模拟 QueryOver。相反,定义一个存储库接口(在内部使用 QueryOver)并模拟该接口。

    【讨论】:

      【解决方案4】:

      我不认为上面的代码是正确的。 AFAIK QueryOver 是 ISession 接口上的扩展方法,您不能像那样模拟扩展方法(至少不能使用 Moq 或 RhinoMocks 等传统模拟工具)。

      【讨论】:

      • QueryOver 不是扩展方法。你在想Query
      【解决方案5】:

      最近我一直在将调用 .QueryOver() 的代码移到受保护的虚拟方法中,并构建我自己的 TestableXYZ 继承自 XYZ 并覆盖该方法并返回一个空列表或其他任何内容。这样我就不需要只用于测试的存储库了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-30
        • 1970-01-01
        • 2011-06-13
        • 2019-09-11
        • 2010-11-16
        • 1970-01-01
        相关资源
        最近更新 更多