存储库本身通常不会被测试?

时间:2011-08-18 16:39:43

标签: .net unit-testing nhibernate entity-framework domain-driven-design

我很抱歉,但我不熟悉存储库模式,单元测试和orm工具。

我一直在研究单元测试和存储库模式,得出了一些结论,我想知道我是不是。

存储库模式有助于在使用它的控制器中替换单元测试,例如,对吗?因为创建上下文(在EF中)或会话(在NH中)更难,对吧?存储库本身未经过测试?为什么呢?

将EntityFramework或NHibernate与存储库模式一起使用,如果我想测试我的存储库,我需要进行集成测试吗?因为如果我使用我的上下文/会话的假实现,我没有进行真正的测试?因为上下文/会话本身就是存储库(我的意思是它们实现了添加,删除,编辑,GetById,GetAll等的真实逻辑)?

使用EF或NH的存储库模式就像一个包装器? (不仅是一个包装器,我知道这是域的导入概念。)

4 个答案:

答案 0 :(得分:14)

Repository接口属于域层。但实现属于基础架构或数据访问层。此实现通常不使用 unit 测试进行测试。它大量使用ORM API,因此单独测试存储库非常困难和浪费。此问题并非特定于存储库:Don't mock types you don't own.

应使用真实的ORM使用集成测试来测试存储库。 In memory数据库是解决此问题的非常popular方法。

  

...因为如果我使用假货   我的上下文/会话的实现我没有做真正的测试?

即使你设法成功(我真的怀疑NHibernate的情况),你也会浪费你的时间。会话/上下文界面不受您的控制,您的测试只会重复您对真实事物如何工作的猜测。

  

因为上下文/会话本身就是存储库吗?

没有。上下文/会话是UnitOfWork模式的实现。它不属于您的域名。这是基础设施。

  

使用EF或NH的存储库模式只是一个包装器吗?

存储库是重要的域概念,它不仅仅是一个“包装器”。就像您的应用程序不是数据库的“包装器”一样。我认为DDD存储库接口定义应尽可能基于Ubiquitous Language,不应包含任何与ORM相关的单词或类型。

答案 1 :(得分:11)

在这种情况下,我会严格区分EF和NH,我不会在同一个问题中包含这两种技术。简单的NH更加成熟,并且具有可以更容易测试的代码的架构。同样在NH的情况下,您可以简单地将数据库切换到另一个(如SQLite),它仍然可以正常工作,在EF的情况下不一定是真的,其中切换数据库可以导致测试完全不同的应用程序 - 特别是如果你在MS和非MS数据库之间切换。

什么是存储库?让我们看看Martin Fowler's definition

  

存储库在域和数据映射层之间进行调解,   表现得像内存中的域对象集合。客户对象   以声明方式构造查询规范并将其提交给   存储库是为了满足。可以添加和删除对象   存储库,因为它们可以从一个简单的对象集合,和   由Repository封装的映射代码将执行   幕后的适当操作。从概念上讲,是一个存储库   封装了持久存储在数据存储中的对象集   对它们执行的操作,提供更面向对象的视图   持久层的。存储库也支持目标   实现域之间的清晰分离和单向依赖   和数据映射层。

很好的定义。现在想想DbSet

的目的
  • 它是否在内存收集中起作用?是的,您可以使用它从数据库中获取实体或使用Local属性来获取已加载的实体。
  • 客户端可以声明性地查询规范吗?是的,它被称为linq-to-entities。
  • 可以从集合中添加或删除对象吗?是的,它可以。
  • 是否封装了映射?是的。
  • 有干净的分离吗?在逻辑方面是的。就API而言,因为将IDbSet暴露给域模型将使域模型依赖于技术 - EF。有问题吗?理论上是的,对于纯粹的是,但在99%中它确实不是问题,因为您需要更改API的情况很少见,即使您正确地分离API,它也总是涉及大的变化。

DbSet是一个存储库。直接使用DbSet并将其包装到某个通用存储库之间的唯一区别是分离。这导致我以前对类似问题的回答 - Generic Repository With EF 4.1 what is the point

现在应用程序中存储库的目的是什么?我看到了您之前的问题,包括this one,其中BaseRepository建立在实体框架之上。如果您认为这是一个基本存储库,它将是您的专业存储库的父级,用于您的域模型的聚合根,并且只公开与特定的公开实体类型相关的专用方法,那么是 - 您正在使用存储库模式,您需要它。但是如果你只是包装上下文和单一集并调用这个存储库,你很可能只创建了具有可疑附加值的冗余层,因为它只是DbSet之上的包装器。

在这种情况下,只有一种方案可以使您的存储库(DbSet包装器)有意义:

  • 包装器永远不会公开IQueryable(linq-to-entities)
  • 包装器永远不会接受Expression<>并将其内部传递给IQueryalbe(linq-to-entities)

这是唯一可以为您提供完全可模拟的存储库的方案=&gt;您的上层可以轻松进行单元测试。您不打算单元测试存储库,也不打算模拟存储库中使用的上下文。存储库包装数据访问和映射逻辑 - 在存储库的情况下,唯一合理的测试是集成测试。这个场景有什么问题?你将失去LINQ的全部功能,你必须包装/重新实现EF中实现的一些方法和类型。这种存储库与使用存储过程包装数据访问时使用的存储库相同。

如果您不遵循该方案,您的生活将变得更加轻松。您将拥有LINQ定义的查询,但您将无法对代码进行单元测试,因为没有mock / fake仍会将查询评估为linq-to-entities。模拟DbSetIQueryable后,您将使用linq-to-object,它是linq-to-entities的超集。您可以轻松编写一个查询,该查询将在模拟的DbSet上通过测试但在运行时使用真实DbSet失败。 Here更多地是关于此问题,here是查询的示例,它将通过测试但在运行时失败。在这种情况下,您必须对存储库顶部使用linq-to-entities查询的所有方法使用集成测试(使用真实数据库)。

答案 2 :(得分:4)

制作存储库非常常见,这些存储库只是数据库访问的轻量级包装器,并将业务方法放在依赖于存储库的实体服务层中,是的。因此,您可以使用包装的存储库(例如,内存中的数据库)对实体服务进行单元测试。

答案 3 :(得分:4)

当单元测试时,您需要清楚您正在测试的“单元”。在测试单位时,你会模仿单位之外的东西。你嘲笑的事情本身可以单独测试。

还有另一种测试方法可以测试较大的碎片。所以你可以测试你的代码+存储库+数据库。这是有价值的,但它没有测试相同的东西。特别是在使用真实数据库时,强制将代码强制转换为某些错误路径。

您应该测试{您的代码+存储库}并模拟数据库吗?取决于存储库本身的复杂性和经过良好测试的程度。