实体框架4.1的假DbContext测试

Aca*_*uza 46 .net tdd asp.net-mvc unit-testing entity-framework

我正在使用本教程伪造我的DbContext并测试: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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我的测试是这样的:

[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);
}
Run Code Online (Sandbox Code Playgroud)

我做得对吗?

Lad*_*nka 122

不幸的是,你做得不对,因为那篇文章是错的.它假装FakeContext将使您的代码单元可测试但不会.一旦暴露IDbSetIQueryable到控制器,你假一组与内存收集你不能确定你的单元测试真正测试你的代码.这是很容易写在你的控制器中的LINQ查询,这将通过单元测试(因为FakeContext使用LINQ到对象),但在运行时失败(因为你的真实语境使用LINQ到实体).这使得您的单元测试的整个目的毫无用处.

我的观点:如果你想将集合暴露给控制器,不要打扰伪造上下文.而是使用集成测试与真实数据库进行测试.这是如何验证控制器中定义的LINQ查询执行预期的唯一方法.

当然,如果你想打电话只是ToListFirstOrDefault在你套你FakeContext会满足你的需要,但一旦你做任何事情更加复杂,你可以很快找到一个陷阱(只是把字符串"不能被翻译成店表达"到谷歌-所有这些只有在运行Linq-to-entities时它们才会出现,但它们会通过Linq-to-objects传递测试.

这是一个非常常见的问题,因此您可以查看其他一些示例:

  • 我同意[布伦特的回答](http://stackoverflow.com/a/8131121/376366).在记忆中伪装DbSets可能无法涵盖所有​​可能性,正如Ladislav所解释的那样,但它是一种有效的单元测试技术.我成功地应用该方法[由Richard加塞德描述](http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context).集成测试将确保端到端方案的工作.重要的是要记住,单元测试和集成测试是互补的,而不是相互排斥的替代方案. (13认同)
  • +1为"无法翻译成商店表达":) (8认同)
  • 单元测试的重点是仅测试该单元.据推测,您将在Web服务中测试类似于业务逻辑的东西,该服务接受DTO,验证它,将其保存到数据库中.根据定义,您不关心对象是否实际持久存储在存储库中,因为这可能会在以后发生变化.您只关心业务逻辑是否正确:是否在存储库上进行了正确的调用,或者(如果对象验证失败)是否抛出了正确的错误等. (3认同)
  • 最好的方法是不要用测试编码逻辑的假DbSet编写单元测试,并编写集成测试来测试组合解决方案在集成时是否真的有效? (2认同)

bre*_*ick 63

"不幸的是,你做得不对,因为那篇文章是错误的.它假装FakeContext将使你的代码单元可测试,但它不会"

我是您所引用的博客文章的创建者.我在这里看到的问题是对N层单元测试的基本原理的误解.我的帖子并不是要直接用于测试控制器逻辑.

单元测试应该完全按照名称暗示并测试"一个单元".如果我正在测试一个控制器(正如你上面所做的那样),我会忘记所有关于数据访问的事情.我应该在脑海中删除所有对数据库上下文的调用,并用黑盒方法调用替换它们,好像这些操作对我来说是未知的.这是我有兴趣测试的那些操作的代码.

例:

在我的MVC应用程序中,我们使用存储库模式.我有一个存储库,比如CustomerRepository:ICustomerRepository,它将执行我的所有Customer数据库操作.

如果我要测试我的控制器,我是否希望测试测试我的存储库,数据库访问和控制器逻辑本身?当然不是!这条管道中有很多"单位".您要做的是创建一个虚拟存储库,它实现ICustomerRepository以允许您单独测试控制器逻辑.

据我所知,这不能单独在数据库上下文中完成.(除了使用Microsoft Moles之外,您可以查看是否需要).这只是因为所有查询都在控制器类的上下文之外执行.

如果我想测试CustomerRepository逻辑,我该怎么做?最简单的方法是使用伪上下文.这将允许我确保当我试图通过id获得客户时,它实际上通过id获取客户等等.存储库方法非常简单,"无法转换为存储表达式"问题通常不会浮出水面.虽然在某些小的情况下它可能(有时由于错误编写的linq查询)在这些情况下,执行集成测试也很重要,这些测试将一直测试代码到数据库.这些问题将在集成测试中找到.我已经使用这种N层技术已经有一段时间了,并且发现没有问题.

集成测试

显然,针对数据库本身测试您的应用程序是一项代价高昂的工作,一旦您获得成千上万的测试就变成了一场噩梦,另一方面,它最能模仿代码在"现实世界"中的使用方式.这些测试也很重要(从ui到数据库),它们将作为集成测试的一部分执行,而不是单元测试.

Acaz,您在场景中真正需要的是一个可模拟/可伪造的存储库.如果您希望像操作那样测试控制器,那么您的控制器应该接收一个包装数据库功能的对象.然后它可以返回您需要的任何内容,以便测试控制器功能的所有方面.

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

为了测试存储库本身(如果在所有情况下都需要进行辩论),您将要么伪造上下文或使用"Moles"框架中的某些内容.

LINQ本质上很难测试.使用扩展方法在上下文之外定义查询的事实为我们提供了很大的灵活性,但却产生了测试的噩梦.将您的上下文包装在存储库中,这个问题就消失了.

对不起这么久:)

  • 我同意这种观点.Richard Garside [描述](http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context)一种实体框架数据上下文模拟的方法,它非常干净且运行良好. (2认同)
  • 为了测试存储库,您需要使用集成测试来验证查询和持久性逻辑以及单元测试以验证其他任何内容.使用伪上下文测试查询真的没有意义.当你需要与真正的提供商进行测试时,为什么要用假写测试呢? (2认同)

Soe*_*Moe 23

正如Ladislav Mrnka所提到的,你应该测试Linq-to-Entity而不是Linq-to-Object.我通常使用Sql CE作为测试数据库,并在每次测试之前始终重新创建数据库.这可能会使测试有点慢,但到目前为止,我对100多个单元测试的性能表示满意.

首先,在测试项目的App.config中使用SqlCe更改连接字符串设置.

<connectionStrings>
    <add name="MyDbContext"
       connectionString="Data Source=|DataDirectory|MyDb.sdf"
         providerName="System.Data.SqlServerCe.4.0"
         />
</connectionStrings>
Run Code Online (Sandbox Code Playgroud)

其次,使用DropCreateDatabaseAlways设置db初始化程序.

Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>());
Run Code Online (Sandbox Code Playgroud)

然后,在运行每个测试之前强制EF进行初始化.

public void Setup() {
    Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>());

    context = new MyDbContext();
    context.Database.Initialize(force: true);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 经过许多小时的实验,这就是我最终做的事情.我发现伪造DbContext(最终伪造IDbSet)并不值得付出努力.这是针对真实数据库(在我的情况下为localdb)测试存储库的唯一真实方式.这种情况模糊了单元测试和集成测试之间的界限,因为虽然你是单元测试你的repo,但它仍在测试较低层(真实上下文),例如集成测试.尽管如此,就我的回购而言,它关注它的"单元"测试,我的控制器的测试将是集成测试. (2认同)

归档时间:

查看次数:

31308 次

最近记录:

9 年,9 月 前