Asp.net mvc 3 单元测试使用NHibernate的MVC控制器,有无实现存储库模式

Asp.net mvc 3 单元测试使用NHibernate的MVC控制器,有无实现存储库模式,asp.net-mvc-3,unit-testing,nhibernate,mocking,repository-pattern,Asp.net Mvc 3,Unit Testing,Nhibernate,Mocking,Repository Pattern,我有一个MVC应用程序,它将NHibernate用于ORM。每个控制器接受一个ISession构造参数,然后该参数用于对域模型对象执行CRUD操作。比如说, public class HomeController : Controller { public HomeController(ISession session) { _session = session; } public ViewResult Index(DateTime minDate

我有一个MVC应用程序,它将NHibernate用于ORM。每个控制器接受一个ISession构造参数,然后该参数用于对域模型对象执行CRUD操作。比如说,

public class HomeController : Controller
{
    public HomeController(ISession session)
    {
        _session = session;
    }
    public ViewResult Index(DateTime minDate, DateTime maxDate)
    {
        var surveys = _session.CreateCriteria<Survey>()
                              .Add( Expression.Like("Name", "Sm%") )
                              .Add( Expression.Between("EntryDate", minDate, maxDate) )
                              .AddOrder( Order.Desc("EntryDate") )
                              .SetMaxResults(10)
                              .List<Survey>();

        // other logic that I want to unit test that does operations on the surveys variable
        return View(someObject);
    }
    private ISession _session;
}
第二种方法更容易进行单元测试,因为IRepository接口比ISession接口更容易模拟,因为它只是一个方法调用。然而,我真的不想走这条路,因为:

1) 创建一个全新的抽象层和更多的复杂性只是为了使单元测试更容易,这似乎是一个非常糟糕的想法,而且

2) 有很多评论反对在nHibernate中使用存储库模式,因为ISession接口已经是一个类似于存储库的接口。(特别是阿耶德的帖子和评论)我倾向于同意这一评论


所以我的问题是,有没有办法通过模拟ISession对象来单元测试我的初始实现?如果不是,我唯一的办法是使用存储库模式包装ISession查询,还是有其他方法可以解决这个问题?

您是否考虑过让您的测试从使用SQLite的基本设备继承

public class FixtureBase
{
    protected ISession Session { get; private set; }
    private static ISessionFactory _sessionFactory { get; set; }
    private static Configuration _configuration { get; set; }

    [SetUp]
    public void SetUp()
    {
        Session = SessionFactory.OpenSession();
        BuildSchema(Session);
    }

    private static ISessionFactory SessionFactory
    {
        get
        {
           if (_sessionFactory == null)
           {
                var cfg = Fluently.Configure()
                    .Database(FluentNHibernate.Cfg.Db.SQLiteConfiguration.Standard.ShowSql().InMemory())
                    .Mappings(configuration => configuration.FluentMappings.AddFromAssemblyOf<Residential>())
                    .ExposeConfiguration(c => _configuration = c);

                _sessionFactory = cfg.BuildSessionFactory();
           }

            return _sessionFactory;
        }
    }

    private static void BuildSchema(ISession session)
    {
        var export = new SchemaExport(_configuration);
        export.Execute(true, true, false, session.Connection, null);
    }

    [TearDown]
    public void TearDownContext()
    {
        Session.Close();
        Session.Dispose();
    }


}
公共类FixtureBase
{
受保护的ISession会话{get;private set;}
私有静态ISessionFactory\u会话工厂{get;set;}
私有静态配置_配置{get;set;}
[设置]
公共作废设置()
{
Session=SessionFactory.OpenSession();
构建模式(会话);
}
私有静态ISessionFactory会话工厂
{
得到
{
if(_sessionFactory==null)
{
var cfg=fluntly.Configure()
.Database(FluentNHibernate.Cfg.Db.SQLiteConfiguration.Standard.ShowSql().InMemory())
.Mappings(配置=>configuration.FluentMappings.AddFromAssemblyOf())
.ExposeConfiguration(c=>_配置=c);
_sessionFactory=cfg.BuildSessionFactory();
}
返回工厂;
}
}
私有静态void BuildSchema(ISession会话)
{
var export=newschemaexport(_配置);
export.Execute(true、true、false、session.Connection、null);
}
[撕裂]
public void TearDownContext()
{
Session.Close();
Session.Dispose();
}
}

奥伦经常四处游荡。他曾经是知识库和工作单元的巨大支持者。他可能会再次转向它,但有一套不同的要求

Repository有一些非常特殊的优势,Oren的评论都没有找到解决方案。此外,他推荐的产品也有自己的局限性和问题。有时我觉得他只是在用一套问题换另一套问题。当您需要提供相同数据的不同视图(例如Web服务或桌面应用程序)同时仍保留Web应用程序时,它也很有用

话虽如此,他有很多优点。我只是不确定是否有好的解决方案

对于高度测试驱动的场景,存储库仍然非常有用。如果您不知道是否将坚持使用给定的ORM或持久性层,并且可能希望将其替换为另一个ORM或持久性层,那么它仍然很有用

奥伦的解决方案倾向于更紧密地融入应用程序。这在许多情况下可能不是问题,在其他情况下可能是


他创建专用查询类的方法很有趣,并且是实现这一目标的第一步,这可能是一个更好的总体解决方案。但软件开发仍然是一门艺术或手工艺,而不是科学。我们仍在学习。

引入带有命名查询方法的存储库不会增加系统的复杂性。实际上,它降低了复杂性,使代码更易于理解和维护。比较原始版本:

public ViewResult Index(DateTime minDate, DateTime maxDate)
{
    var surveys = _session.CreateCriteria<Survey>()
                          .Add(Expression.Like("Name", "Sm%"))
                          .Add(Expression.Between("EntryDate", minDate, maxDate))
                          .AddOrder(Order.Desc("EntryDate"))
                          .SetMaxResults(10)
                          .List<Survey>();

     // other logic which operates on the surveys variable
     return View(someObject);
}
我不费吹灰之力就明白这里发生了什么。该方法具有单一职责和单一抽象级别。所有与数据访问相关的逻辑都消失了。查询逻辑不会在不同的位置重复。实际上,我不在乎它是如何实现的。如果此方法的主要目标是
其他一些逻辑
,我是否应该关心呢

当然,您可以毫不费力地为业务逻辑编写单元测试(另外,如果您正在使用TDD repository,那么您可以在实际编写数据访问逻辑之前测试控制器,并且在开始编写存储库实现时,您已经设计了存储库接口):

[测试]
公共空间应该是dootherlogic()
{
//安排
Mock repository=newmock();
repository.Setup(r=>r.SearchSurveyByDate(minDate,maxDate))
.报告(调查);
//表演
HomeController=新的HomeController(repository.Object);
ViewResult结果=controller.Index(minDate,maxDate);
//断言
}
顺便说一句,内存中数据库的使用对于验收测试是很好的,但是对于单元测试,我认为这是一种过分的使用

还可以看看NHibernate 3.0中的or QueryOver,它使用表达式而不是字符串来构建标准。如果重命名某个字段,则数据访问代码不会中断


还可以查看传递的最小/最大值对。

谢谢您的评论。我可以看到使用存储库模式的利弊。它的使用大大简化了单元测试。但是,它需要额外的抽象层,并且直接针对控制器中的ISession接口编写代码非常方便和灵活。然而,您的应用程序与NHibernate的耦合非常紧密。我想没有完美的解决方案,一切都是取舍,取决于什么是最重要的
public class FixtureBase
{
    protected ISession Session { get; private set; }
    private static ISessionFactory _sessionFactory { get; set; }
    private static Configuration _configuration { get; set; }

    [SetUp]
    public void SetUp()
    {
        Session = SessionFactory.OpenSession();
        BuildSchema(Session);
    }

    private static ISessionFactory SessionFactory
    {
        get
        {
           if (_sessionFactory == null)
           {
                var cfg = Fluently.Configure()
                    .Database(FluentNHibernate.Cfg.Db.SQLiteConfiguration.Standard.ShowSql().InMemory())
                    .Mappings(configuration => configuration.FluentMappings.AddFromAssemblyOf<Residential>())
                    .ExposeConfiguration(c => _configuration = c);

                _sessionFactory = cfg.BuildSessionFactory();
           }

            return _sessionFactory;
        }
    }

    private static void BuildSchema(ISession session)
    {
        var export = new SchemaExport(_configuration);
        export.Execute(true, true, false, session.Connection, null);
    }

    [TearDown]
    public void TearDownContext()
    {
        Session.Close();
        Session.Dispose();
    }


}
public ViewResult Index(DateTime minDate, DateTime maxDate)
{
    var surveys = _session.CreateCriteria<Survey>()
                          .Add(Expression.Like("Name", "Sm%"))
                          .Add(Expression.Between("EntryDate", minDate, maxDate))
                          .AddOrder(Order.Desc("EntryDate"))
                          .SetMaxResults(10)
                          .List<Survey>();

     // other logic which operates on the surveys variable
     return View(someObject);
}
public ViewResult Index(DateTime minDate, DateTime maxDate)
{
    var surveys = _repository.SearchSurveyByDate(minDate, maxDate);
    // other logic which operates on the surveys variable
    return View(someObject);
}
[Test]
public void ShouldDoOtherLogic()
{
    // Arrange
    Mock<ISurveryRepository> repository = new Mock<ISurveryRepository>();
    repository.Setup(r => r.SearchSurveyByDate(minDate, maxDate))
              .Returns(surveys);

    // Act
    HomeController controller = new HomeController(repository.Object);
    ViewResult result = controller.Index(minDate, maxDate);

    // Assert
}