使用Entity Framework进行单元测试

71

我想测试使用Entity Framework构建的实体。 我的担忧是使用Entity Framework意味着直接处理数据源。 那么,有什么方法可以对基于Entity Framework的组件进行单元测试吗?


可能是[如何使用Entity Framework 6进行单元测试,是否值得费心?]的重复问题(https://dev59.com/52Eh5IYBdhLWcg3wMA_M)。 - Liath
13个回答

50

现在要去阅读了。许多EF测试问题和答案都不是最新的,因此EF 4.0链接加1。 - StuperUser
1
我们的商店开始实施在这里看到的技术,目前看起来似乎是正确的答案。 - Dan Bailiff
3
FYI,他从未在UnitOfWork类中处理上下文。在这个例子中,http://www.asp.net/entity-framework/tutorials/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application,UnitOfWork类实现了IDisposable。将这两种方法结合起来,使用IDisposable修饰Hans答案中的IUnitOfWork接口,并实现Dispose模式,例如:http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.71).aspx。 - James McLachlan

7

6
一种廉价的方法是设置一个与真实数据库结构相同的数据库文件,并将连接字符串在单元测试配置中指向该文件。数据库不需要拥有真实数据库所有的表,只需要有单元测试所需的表。
缺点是需要管理数据库状态,以便单元测试在运行期间和之间不会互相影响或影响自身。
我知道当真实数据库和单元测试数据库都使用SQL Express时,这种方法是可行的,但我不知道如何为完整的SQL数据库制作SqlExpress DB存根。
我意识到这实际上是集成测试,但它可能比重构代码或学习模拟框架更便宜。
例如,真实连接字符串:
<add name="DrinksEntities" 
     connectionString="metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient
     ;provider connection string=&quot;Data Source=localhost\sqlexpress;Initial Catalog=Drinks2;Integrated Security=True;MultipleActiveResultSets=True;Application Name=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />

例子:单元测试连接字符串:

<add name="DrinksEntities" 
     connectionString="metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient
     ;provider connection string=&quot;Data Source=.\SQLEXPRESS;attachdbfilename=|DataDirectory|\Inventory.mdf;Integrated Security=True;user instance=True;MultipleActiveResultSets=True;Application Name=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />

4
目前,这对我来说似乎是最佳方案。如果想加速数据库,将整个数据库运行在 RAM 中。这些是端到端测试。模拟解决方案根本不是解决方案。测试数据库的目的不仅是测试持久存储,而且还要测试查询。只有实际执行查询测试时才有意义,模拟在这里完全是浪费时间——你需要虚假的表数据。我曾尝试为 Linq2Sql 创建整套虚假存储库,但这是浪费时间,因为使用左连接的查询无法测试——我担心 EF 也存在同样的问题。 - user1040323
2
设置数据非常容易 - 阅读微软有关单元测试存储过程的建议。同样的原则适用于此 - 您可以启动事务,截断表格,并插入所需的测试数据;然后运行测试,最后回滚事务。数据库最终处于与开始时相同的状态,您可以为每个测试管理测试数据。 - gbjbaanb
3
这似乎是完全测试我的应用程序的唯一方法。模拟IRepository将让您测试使用数据访问代码的代码,但不是实际的数据访问代码,而这就是我90%错误的地方。 - Britton

4

8
使用 EF 很难轻松地完成这个任务。对于接口或可扩展的 POCO,你的答案是正确的。 - user1228

4
由于Entity Framework版本1违反了几个重要的软件设计原则,因此在应用程序中使用它时,真的没有任何方法可以应用TDD。我的研究指向NHibernate,如果你正在寻找一个即时的解决方案。它是为单元测试而设计的。
然而,如果你能等待,下一个版本的Entity Framework似乎有希望: 使用Entity Framework 4.0进行TDD演示

4

3
尽管这些示例可能非常简单,但我已经尝试讨论了一个可能的解决方案。它涉及关注点分离和我们亲爱的朋友依赖注入。

http://devblog.petrellyn.com/

如果您想了解更多细节,请联系我。


2

我同意,你需要一个模拟框架。你可以创建“模拟”对象,这些对象不是从数据源中检索出来的,并且你可以测试该对象中的数据。我个人一直在使用Moq,并且我很喜欢它——还有Rhinomocks等其他框架。


2

在这个问题上我曾经非常沮丧,但现在我终于找到了一个解决方案,至少对于其中的一部分问题我感到很满意。

首先使用一个类似于仓库接口的东西:

public interface IRepository
{
    IQueryable<T> GetObjectSet<T>();
}

我们可以使用它来返回内存集合或真正的DB支持集合。接下来,将您的查询封装到一个查询对象中,其接口看起来像这样。
public interface IQuery<T>
{
    IQueryable<T> DoQuery(IQueryable<T> collection);
}

现在将您的单元测试分成两组。第一组将测试您的查询是否有效。操作如下:
[TestMethod]
public void TestQueryFoo()
{
     using(var repo = new SqlRepository("bogus connection string"))
     {
         var query = new FooQuery(); // implements IQuery<Foo>
         var result = query.DoQuery(repo.GetObjectSet<Foo>());  // as long as we don't enumerate the IQueryable EF won't notice that the connection string is bogus
         var sqlString = ((System.Data.Objects.ObjectQuery)query).ToTraceString(); // This will throw if the query can't be compiled to SQL
     }
}

第二组单元测试可以自由地测试您的业务逻辑,而不必担心SQL编译步骤,这是我们遇到最多问题的地方。

尽管它并不完美,触发器显然不会被运行,DB实现的约束可能会被违反,并且上下文和数据库不同步的一些问题可能会出现。因此,虽然仍然需要端到端的集成测试,但有可能在简单的单元测试中捕获IMO最常见的运行时问题。


请您能否提供使用这种方法的一些示例代码? 我还想对一个使用Entity Framework的Web API 2项目进行单元测试。 - Sebastian

0

这里是工作单元模式+内存数据库+t4代码生成的聚合,用于自动生成虚假的EF dbContext。

http://mockingcompetence.wordpress.com/2013/05/20/fakingefdatacontext/

目前在完全复制真实的EF数据库连接时存在一些问题(无效的linq to EF查询和没有外键强制执行)。

然而,拥有一个内存上下文以快速运行单元测试几乎是进行TDD或任何其他种类的单元测试中心方法所必需的。

随着我解决更多问题,我将在上述链接中发布更新。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接