存储库本身通常不经过测试?
抱歉,我对存储库模式、单元测试和 orm 工具还不熟悉。
我一直在研究单元测试和存储库模式,并得出一些结论,我想知道我是否正确。
存储库模式有助于在使用它的控制器中替换单元测试,例如,对吧?因为创建上下文(在 EF 中)或会话(在 NH 中)的存根/伪造比较困难,对吧?存储库本身没有经过测试?为什么?
使用 EntityFramework 或 NHibernate 与存储库模式,如果我想测试我的存储库,我需要进行集成测试吗?因为如果我使用上下文/会话的假实现,我就不会进行真正的测试?因为上下文/会话本身就是存储库(我的意思是它们实现了添加、删除、编辑、GetById、GetAll 等真正的逻辑)?
带有 EF 或 NH 的存储库模式就像一个包装器? (不仅仅是一个包装器,我知道这是域的一个重要概念。)
I'm sorry but I'm new to repositories patterns, unit tests and orm tools.
I've been researching on unit tests and the repository pattern, and arrived at some conclusions, I wonder if I'm right.
The repository pattern facilitates unit testing to be replaced in controller that make use of it for example, right? Because create a stub/fake of context(in EF) or session(in NH) is harder, right? The repository itself is not tested? Why?
Using EntityFramework or NHibernate with repository pattern, if i want to test my repositories I need to do integration tests? Because if I use a fake implementation of my context/session I'm not doing real tests? Because the context/session itself is the repository (I mean they implement the real logic of Add, Remove, Edit, GetById, GetAll, ..)?
The repository pattern with EF or NH is like a wrapper? (Not only a wrapper, I know this is a import concept of the domain.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
Repository接口属于领域层。但实现属于基础设施或数据访问层。此实现通常不通过单元测试进行测试。它大量使用 ORM API,因此单独测试存储库非常困难且浪费。此问题并非特定于存储库: Don'不要模拟您不拥有的类型。
存储库应该使用真正的 ORM 通过集成测试进行测试。 内存数据库是一个非常解决这个问题的流行方法。
即使你设法做到了(我真的怀疑 NHibernate 的情况),你也会浪费时间。会话/上下文接口不受您的控制,您的测试只会重申您对真实事物如何工作的猜测。
不。 Context/Session 是 UnitOfWork 模式的实现。它不是您的域的一部分。这是基础设施。
存储库是重要的领域概念,它不仅仅是一个“包装器”。就像您的应用程序不是数据库的“包装器”一样。我认为 DDD 存储库接口定义应该尽可能基于通用语言,并且不应该包含任何ORM 相关的单词或类型。
Repository interface belongs to domain layer. But implementation belongs to infrastructure or data access layer. This implementation is usually not tested with unit test. It uses ORM API heavily, so it is extremely hard and wasteful to test repository in isolation. This problem is not specific to repositories: Don't mock types you don't own.
Repository should be tested with integration tests, using real ORM. In memory database is a very popular approach to this problem.
Even if you manage to make it (which I really doubt in NHibernate's case) you will be wasting your time. Session/Context interface is out of your control and your test would just reiterate your guess about how the real thing will work.
No. Context/Session is implementation of UnitOfWork pattern. It is not part of your domain. This is infrastructure.
Repository is important domain concept, it is not just a 'wrapper'. Just like your application is not a 'wrapper' over the database. I think that DDD repository interface definition should be based on Ubiquitous Language as much as possible and should not include any ORM related words or types.
在这种情况下,我会严格区分 EF 和 NH,并且我不会在同一问题中包含这两种技术。 Simple NH 更加成熟,并且具有导致代码更容易测试的架构。另外,在 NH 的情况下,您可以简单地将数据库切换到另一个数据库(例如 SQLite),它仍然可以正常工作,而在 EF 的情况下则不一定如此,因为切换数据库可能会导致测试完全不同的应用程序 - 特别是如果您可以在 MS 和非 MS 数据库之间切换。
什么是存储库?让我们看看 Martin Fowler 的定义:
很好的定义。现在考虑一下
DbSet
的用途:Local
属性获取已加载的实体。IDbSet
暴露给域模型将使域模型依赖于技术 - EF。是不是有问题?理论上是的,对于纯粹主义者来说是的,但在 99% 的情况下这确实不是问题,因为需要更改 API 的情况很少见,而且即使您正确地分离了 API,它也总是涉及很大的更改。DbSet
是一个存储库。直接使用 DbSet 和将其包装到某个通用存储库中的唯一区别是分离。这导致我以前对类似问题的回答 - EF 4.1 的通用存储库有什么意义现在,存储库在您的应用程序中的用途是什么?我看到您之前的问题,包括这个 您的
BaseRepository
构建在实体框架之上。如果您认真地将其视为一个基础存储库,它将成为您的专用存储库的父级,该存储库致力于域模型的聚合根并公开仅与特定公开实体类型相关的专用方法,那么是的 - 您正在使用存储库模式并且您需要它。但是,如果您只是包装上下文和单个集合并调用此存储库,您很可能只创建了具有可疑附加值的冗余层,因为那只是 DbSet 之上的包装器。在这种情况下,只有一种情况您的存储库(
DbSet
包装器)才有意义:IQueryable
(linq-to-entities)Expression<>
并将其内部传递给IQueryalbe
(linq-to-entities)这是唯一一个为您提供完全可模拟存储库的场景=>您的上层可以轻松进行单元测试。您不会对存储库进行单元测试,也不会模拟存储库中使用的上下文。存储库包装数据访问和映射逻辑 - 对于存储库,唯一合理的测试是集成测试。这个场景有什么问题?您将失去 LINQ 的全部功能,并且必须包装/重新实现 EF 中实现的一些方法和类型。这种类型的存储库与使用存储过程包装数据访问时使用的存储库相同。
如果你不遵循这种情况,你的生活就会容易得多。您将拥有由 LINQ 定义的查询,但您将无法对代码进行单元测试,因为没有模拟/伪造仍会将查询评估为 linq-to-entities。一旦您模拟了 DbSet 或 IQueryable,您将使用 linq-to-object,它是 linq-to-entities 的超集。您可以轻松编写一个查询,该查询将通过模拟
DbSet
的测试,但在运行时使用真实的DbSet
会失败。 这里是有关此问题的更多信息这里是将通过的查询示例测试但在运行时失败。在这种情况下,您必须对在存储库顶部使用 linq-to-entities 查询的所有方法使用集成测试(使用真实数据库)。I would strictly differ between EF and NH in this case and I would not include both technologies in the same question. Simple NH is more mature and has architecture leading to code which can be more easily tested. Also in case of NH you can simply switch the database to another one (like SQLite) and it will work still the same which doesn't have to be true in case of EF where switching database can result in testing completely different application - especially if you switch between MS and non-MS database.
What is the repository? Let see Martin Fowler's definition:
Nice definition. Now think about the purpose of
DbSet
:Local
property to get already loaded entities.IDbSet
to domain model will make the domain model dependent on technology - EF. Is it problem? In theory yes, for purist yes but in 99% it is really not a problem because situation where you need to change the API is rare and it always involves big changes even if you correctly separated APIs.DbSet
is a repository. The only difference between usingDbSet
directly and wrapping it into some generic repository is a separation. This leads to my former answer to similar question - Generic Repository With EF 4.1 what is the pointNow what is a purpose of the repository in your application? I saw your previous questions including this one where you have your
BaseRepository
built on top of Entity framework. If you seriously mean this as a base repository which will be parent for your specialized repositories working on aggregate roots for your domain model and exposing specialized methods related only to specific exposed entity type then yes - you are using repository pattern and you need it. But if you are just wrapping context and single set and call this repository you most probably created only redundant layer with doubtful added value because that is just wrapper on top ofDbSet
.There is only single scenario where your repository (
DbSet
wrapper) will make sense in such case:IQueryable
(linq-to-entities)Expression<>
and pass it internally toIQueryalbe
(linq-to-entities)This is the only scenario which will offer you fully mockable repositories => your upper layer can be easily unit tested. You are not going to unit test repositories and you are not going to mock context used in repositories. Repositories wrap data access and mapping logic - the only reasonable tests in case of repositories are integration tests. What is problem of this scenario? You will lose whole power of LINQ and you will have to wrap / re-implement some methods and types implemented in EF. This kind of repositories is the same as used when wrapping data access using stored procedures.
If you don't follow that scenario your live will be much easier. You will have queries defined by LINQ but you will not be able to unit test the code because there is no mock / fake which will still evaluate queries as linq-to-entities. Once you mock
DbSet
orIQueryable
you will use linq-to-object which is superset of linq-to-entities. You can easily write a query which will pass a test on mockedDbSet
but fail at runtime with a realDbSet
. Here is more about this problem and here is the example of query which will pass the test but fail at runtime. In this case you must use integration tests (with real database) for all methods using linq-to-entities queries on top of your repositories.创建存储库只是数据库访问的轻量级包装器,并将业务方法放入依赖于存储库的实体服务层中,这是很常见的。因此,您可以使用包装存储库(例如内存数据库)来对实体服务进行单元测试。
It's pretty common to make repositories which are just lightweight wrappers of your DB access and put business methods in an entity service layer which depends on the repositories, yes. So you can unit test the entity services by using repositories which wrap, e.g., an in-memory DB.
当单元测试时,您需要明确您正在测试的“单元”。在测试您的单元时,您可以模拟单元之外的事物。您嘲笑的事物本身可能会被单独测试。
还有另一种测试,可以测试较大的部件。所以你可以测试你的代码+存储库+数据库。这很有价值,但它并没有测试相同的东西。特别是在使用真实数据库时,很难强制您的代码走上某些错误路径。
您应该测试{您的代码+存储库}并模拟数据库吗?取决于存储库本身的复杂程度和测试程度。
When Unit Testing you need to be clear what "Unit" you are testing. You mock things that are outside of your Unit while you test your Unit. Things you mock may themselves be tested separately.
There's another kind of testing where you test larger pieces. So you might test your code + repository + database. There's value in that but it's not testing the same things. In particular when using a real database it's harder to force your code down some error paths.
Should you test {your code + repository} and mock the database? Depends how complex and how well-tested the repository is in its own right.