假的DbContext的entity framework4.1来testing

我正在使用这个教程来伪造我的DbContext并testing: http ://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic -repository /

但是我必须改变FakeMainModuleContext实现在我的控制器中使用:

public class FakeQuestiona2011Context : IQuestiona2011Context { private IDbSet<Credencial> _credencial; private IDbSet<Perfil> _perfil; private IDbSet<Apurador> _apurador; private IDbSet<Entrevistado> _entrevistado; private IDbSet<Setor> _setor; private IDbSet<Secretaria> _secretaria; private IDbSet<Pesquisa> _pesquisa; private IDbSet<Pergunta> _pergunta; private IDbSet<Resposta> _resposta; public IDbSet<Credencial> Credencial { get { return _credencial ?? (_credencial = new FakeDbSet<Credencial>()); } set { } } public IDbSet<Perfil> Perfil { get { return _perfil ?? (_perfil = new FakeDbSet<Perfil>()); } set { } } public IDbSet<Apurador> Apurador { get { return _apurador ?? (_apurador = new FakeDbSet<Apurador>()); } set { } } public IDbSet<Entrevistado> Entrevistado { get { return _entrevistado ?? (_entrevistado = new FakeDbSet<Entrevistado>()); } set { } } public IDbSet<Setor> Setor { get { return _setor ?? (_setor = new FakeDbSet<Setor>()); } set { } } public IDbSet<Secretaria> Secretaria { get { return _secretaria ?? (_secretaria = new FakeDbSet<Secretaria>()); } set { } } public IDbSet<Pesquisa> Pesquisa { get { return _pesquisa ?? (_pesquisa = new FakeDbSet<Pesquisa>()); } set { } } public IDbSet<Pergunta> Pergunta { get { return _pergunta ?? (_pergunta = new FakeDbSet<Pergunta>()); } set { } } public IDbSet<Resposta> Resposta { get { return _resposta ?? (_resposta = new FakeDbSet<Resposta>()); } set { } } public void SaveChanges() { // do nothing (probably set a variable as saved for testing) } } 

我的testing就是这样的:

 [TestMethod] public void IndexTest() { IQuestiona2011Context fakeContext = new FakeQuestiona2011Context(); var mockAuthenticationService = new Mock<IAuthenticationService>(); var apuradores = new List<Apurador> { new Apurador() { Matricula = "1234", Nome = "Acaz Souza Pereira", Email = "acaz@telecom.inf.br", Ramal = "1234" }, new Apurador() { Matricula = "4321", Nome = "Samla Souza Pereira", Email = "samla@telecom.inf.br", Ramal = "4321" }, new Apurador() { Matricula = "4213", Nome = "Valderli Souza Pereira", Email = "valderli@telecom.inf.br", Ramal = "4213" } }; apuradores.ForEach(apurador => fakeContext.Apurador.Add(apurador)); ApuradorController apuradorController = new ApuradorController(fakeContext, mockAuthenticationService.Object); ActionResult actionResult = apuradorController.Index(); Assert.IsNotNull(actionResult); Assert.IsInstanceOfType(actionResult, typeof(ViewResult)); ViewResult viewResult = (ViewResult)actionResult; Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(IndexViewModel)); IndexViewModel indexViewModel = (IndexViewModel)viewResult.ViewData.Model; Assert.AreEqual(3, indexViewModel.Apuradores.Count); } 

我正在做对吗?

不幸的是,你没有做正确的,因为那篇文章是错误的。 它假装FakeContext会使你的代码单元可testing,但它不会。 一旦你暴露了IDbSet或者IQueryable到你的控制器,并且伪造了内存集合中的集合,你永远不能确定你的unit testing是否真的testing了你的代码。 在您的控制器中编写一个LINQ查询非常简单,因为FakeContext使用LINQ到对象,但在运行时会失败(因为您的真实上下文使用LINQ-to-Entities)。 这使得你的unit testing的整个目的是无用的。

我的意见:如果你想暴露集合到控制器,不要打扰伪造上下文。 而是使用集成testing与真实数据库进行testing。 这是如何validation在控制器中定义的LINQ查询完成您所期望的唯一方法。

当然,如果你想在你的设置上调用ToList或者FirstOrDefault ,你的FakeContext会很好的为你服务,但是一旦你做了更复杂的事情,你可以很快find一个陷阱(只要把string“不能被翻译成商店expression式” – 只有当你运行Linq-to-entities时,所有这些问题才会出现,但它们会通过Linq-to-objects的testing)。

这是很常见的问题,所以你可以检查一些其他的例子:

  • 返回IQueryable或不返回IQueryable
  • unit testingDbContext
  • ASP.NET MVC3和entity framework代码第一架构
  • 在组织方面,当我使用entity framework代码时,我应该在哪里进行常见的查询?
  • 是否有可能存在entity framework上下文和类来testing数据访问层?

“不幸的是你没有做正确的,因为这篇文章是错误的,它假装FakeContext将使你的代码单元可testing,但它不会”

我是你提到的博客文章的创build者。 我在这里看到的问题是对N层unit testing的基本原理的误解。 我的post不是直接用来testing控制器的逻辑。

unit testing应该像名字所暗示的那样完成,并testing“一个单元”。 如果我正在testing一个控制器(就像以上那样),我忘了所有关于数据访问的事情。 我应该删除所有对数据库上下文的调用,并用黑盒子方法调用它们,就好像这些操作对我来说是未知的。 这是我有兴趣testing的那些操作的代码。

例:

在我的MVC应用程序中,我们使用存储库模式。 我有一个仓库,说CustomerRepository:ICustomerRepository,这将执行我所有的客户数据库操作。

如果我要testing我的控制器是否需要testing来testing我的存储库,我的数据库访问和控制器逻辑本身? 当然不是! 这条线上有很多“单位”。 你想要做的是创build一个虚假的仓库,实现ICustomerRepository允许你独立地testing控制器的逻辑。

就我所知,这不能单独在数据库上下文中完成。 (除了可能使用微软莫尔斯,你可以检查,如果你想)。 这只是因为所有的查询都是在你的控制器类的上下文之外执行的。

如果我想testingCustomerRepository逻辑,我该怎么做? 最简单的方法是使用假上下文。 这将允许我确保当我试图通过id获得一个客户时,它实际上通过id等获得客户。 存储库方法非常简单,“不能转换成存储expression式”的问题通常不会出现。 尽pipe在一些小的情况下,它可能(有时是由于错误地编写了linq查询)在这种情况下,也执行集成testing是非常重要的,这些testing将一直到数据库testing您的代码。 这些问题将在集成testing中发现。 我已经使用了这种N层技术已经有一段时间了,并没有发现这个问题。

集成testing

显然,根据数据库testing你的应用程序本身是一个代价高昂的工作,一旦你获得了数以万计的testing成为一场噩梦,另一方面,它最好地模仿了如何在“现实世界”中使用代码。 这些testing也很重要(从ui到数据库),它们将作为集成testing的一部分执行,而不是unit testing。

Acaz,你真正需要在你的场景是一个可嘲弄/伪造的存储库。 如果你想testing你的控制器,那么你的控制器应该接受一个包装数据库function的对象。 然后它可以返回任何你需要它来testing你的控制器function的所有方面。

请参阅http://msdn.microsoft.com/en-us/library/ff714955.aspx

为了testing版本库本身(如果需要,在所有情况下都会进行辩论),您将要么伪造上下文,要么沿着“Moles”框架使用某些东西。

LINQ本质上很难testing。 查询是使用扩展方法在上下文之外定义的,这给我们提供了很大的灵活性,但却造成了testing的噩梦。 将你的上下文包装在一个仓库中,这个问题就会消失。

对不起,这么久:)

正如Ladislav Mrnka所说,你应该testingLinq-to-Entity,而不是Linq-to-Object。 我通常使用Sql CE作为testing数据库,并且总是在每次testing之前重新创build数据库。 这可能会使testing有点慢,但到目前为止,我还没有完成100多个unit testing的性能。

首先,在你testing项目的App.config中用SqlCe改变连接string设置。

 <connectionStrings> <add name="MyDbContext" connectionString="Data Source=|DataDirectory|MyDb.sdf" providerName="System.Data.SqlServerCe.4.0" /> </connectionStrings> 

其次,使用DropCreateDatabaseAlways设置数据库初始值设定项 。

 Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>()); 

然后,在运行每个testing之前强制EF初始化。

 public void Setup() { Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>()); context = new MyDbContext(); context.Database.Initialize(force: true); } 

如果您使用的是xunit,请在构造函数中调用Setup方法。 如果您正在使用MSTest,请将TestInitializeAttribute放在该方法上。 如果nunit …….

您可以使用Effort for EF 6+创build一个假DbContext。 请参阅https://effort.codeplex.com/ 。 Effort代表entity framework的基础实现。

有关工作示例的文章,请参阅http://www.codeproject.com/Tips/1036630/Using-Effort-Entity-Framework-Unit-Testing-Tool或http://www.codeproject.com/Articles/ 460175 / Two-strategies-for-testing-Entity-Framework-Effort?msg = 5122027#xx5122027xx 。

我知道我们不应该这样做,但是有时你必须这样做(你的老板可能也会问你,并且不会改变主意)。

所以我必须这样做,我把它留在这里可能会帮助一些人。 我对c#/ .net相当陌生,所以我认为它远非优化/干净,但它似乎工作。

下面的MSDN 在这里find丢失的类,并使用一点reflection我设法添加一个方法属性:这里的关键元素是AddNavigationPropertyRefreshNavigationProperties 。 如果有人有改善这个代码的build议,我会很乐意接受他们

 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Linq.Expressions; namespace MockFactory { public class TestDbSet<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity> where TEntity : class { public readonly ObservableCollection<TEntity> _data; private readonly IQueryable _query; private readonly Dictionary<Type, object> entities; public TestDbSet() { _data = new ObservableCollection<TEntity>(); _query = _data.AsQueryable(); entities = new Dictionary<Type, object>(); } public override ObservableCollection<TEntity> Local { get { return _data; } } IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator() { return new TestDbAsyncEnumerator<TEntity>(_data.GetEnumerator()); } IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator() { return _data.GetEnumerator(); } Type IQueryable.ElementType { get { return _query.ElementType; } } Expression IQueryable.Expression { get { return _query.Expression; } } IQueryProvider IQueryable.Provider { get { return new TestDbAsyncQueryProvider<TEntity>(_query.Provider); } } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } public void AddNavigationProperty<T>(DbSet<T> dbSet) where T : class { entities.Add(typeof (T), dbSet); } public void RefreshNavigationProperty(TEntity item) { foreach (var entity in entities) { var property = item.GetType().GetProperty(entity.Key.Name); var type = (int)item.GetType().GetProperty(entity.Key.Name.Replace(typeof(TEntity).Name, "")).GetValue(item); var dbSets = (IEnumerable<object>)entity.Value.GetType().GetField("_data").GetValue(entity.Value); var dbSet = dbSets.Single(x => (int)x.GetType().GetProperty("Id").GetValue(x) == type); property.SetValue(item, dbSet); } } public override TEntity Add(TEntity item) { RefreshNavigationProperty(item); _data.Add(item); return item; } public override TEntity Remove(TEntity item) { _data.Remove(item); return item; } public override TEntity Attach(TEntity item) { _data.Add(item); return item; } public override TEntity Create() { return Activator.CreateInstance<TEntity>(); } public override TDerivedEntity Create<TDerivedEntity>() { return Activator.CreateInstance<TDerivedEntity>(); } } } 

然后你可以创build你的上下文

  public TestContext() { TypeUsers = new TestDbSet<TypeUser>(); StatusUsers = new TestDbSet<StatusUser>(); TypeUsers.Add(new TypeUser {Description = "FI", Id = 1}); TypeUsers.Add(new TypeUser {Description = "HR", Id = 2}); StatusUsers.Add(new StatusUser { Description = "Created", Id = 1 }); StatusUsers.Add(new StatusUser { Description = "Deleted", Id = 2 }); StatusUsers.Add(new StatusUser { Description = "PendingHR", Id = 3 }); Users = new TestDbSet<User>(); ((TestDbSet<User>) Users).AddNavigationProperty(StatusUsers); ((TestDbSet<User>)Users).AddNavigationProperty(TypeUsers); } public override DbSet<TypeUser> TypeUsers { get; set; } public override DbSet<StatusUser> StatusUsers { get; set; } public override DbSet<User> Users { get; set; } public int SaveChangesCount { get; private set; } public override int SaveChanges(string modifierId) { SaveChangesCount++; return 1; } } 

最后不要忘记在你的testing中刷新导航属性之前做断言(应该有一个更好的办法,但我找不到)

 ContextFactory.Entity.Users.Each(((TestDbSet<User>) ContextFactory.Entity.Users).RefreshNavigationProperty);