单元测试DbContext

Nel*_*eis 50 unit-testing entity-framework-4.1

我研究了一些关于我可以用来对DbContext进行单元测试的技术的信息.我想在上下文中添加一些内存数据,以便我的测试可以针对它运行.我正在使用Database-First方法.

我发现最有用的两篇文章是这个这个.该方法依赖于创建一个MyContext和FakeContext都将实现的IContext接口,允许模拟上下文.

但是,正如某些人所指出 那样,我试图避免使用存储库来抽象EF,因为EF 4.1已经通过DbSet和DbContext实现了存储库和工作单元模式,我真的想保留EF实现的所有功能.团队无需使用通用存储库维护它们,正如我在其他项目中所做的那样(这有点痛苦).

使用IContext会引导我走同一条路(或者不是吗?).

我考虑创建一个继承自主MyContext的FakeContext,从而利用它下面的DbContext来运行我的测试,而不需要访问数据库.我找不到类似的实现,所以我希望有人可以帮助我.

我做错了什么,或者这会导致我遇到一些我没想到的问题?

Lad*_*nka 36

问自己一个问题:你打算测试什么?

你提到FakeContext并嘲笑上下文 - 为什么同时使用它们?这些只是做同样的不同方式 - 提供仅测试上下文的实现.

还有一个更大的问题 - 伪造或模拟上下文或集合只有一个结果:您不再测试您的真实代码了.

简单的例子:

public interface IContext : IDisposable
{
     IDbSet<MyEntity> MyEntities { get; }
}

public class MyEntity
{
    public int Id { get; set; }
    public string Path { get; set; } 
}

public class MyService
{
    private bool MyVerySpecialNetMethod(e)
    {
        return File.Exists(e.Path);
    }

    public IEnumerable<MyEntity> GetMyEntities()
    {
        using (IContext context = CreateContext())
        { 
            return context.MyEntities
                          .Where(e => MyVerySpecialNetMethod(e))
                          .Select(e)
                          .ToList();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在想象一下,你的SUT中有这个(被测系统 - 在单元测试的情况下,它是一个单位=通常是一个方法).在测试代码,您提供FakeContextFakeSet和它的工作-你将有一个绿色的测试.现在在生产代码中,您将提供另一个派生的DbContext,DbSet并且您将在运行时获得异常.

为什么?因为通过使用FakeContext你也改变了LINQ提供程序,而不是LINQ to Entities,你正在运行LINQ to Objects,所以调用无法转换为SQL的本地.NET方法以及LINQ to Entities中没有的许多其他LINQ功能!您还可以通过数据修改找到其他问题 - 参照完整性,级联删除等.这就是为什么我认为处理context/LINQ to Entities的代码应该用集成测试覆盖并针对真实数据库执行的原因.

  • 关于更喜欢进行集成测试,我不同意这是测试使用EF的代码的唯一方法.我相信能够做到这两点很重要.当单元测试恰好使用EF的业务逻辑时,您不希望通过执行集成测试来测试您的模型是否与数据库同步.这是单元测试的定义.显然,集成测试很重要,但它是一种单独的测试类型.我写了一个替代答案,解释了如何进行单元测试. (12认同)
  • 我已经阅读了很多关于如何模拟ObjectContext的问答,但所有这些都感觉"不完整".根据拉迪斯拉夫的回答,我现在知道气味在哪里.你已经很好地指出了它:"还有一个更大的问题 - 伪造或模拟上下文或集合只有一个结果:你不再测试你的真实代码了." ---不仅是我的!! EF有很多错综复杂的东西,我实际上想要测试他们的ObjectContext实现,因为我偶尔偶然发现了一些非常愚蠢的东西,或者一些没有人认为会丢失的缺失特征.Thx (4认同)
  • @WiktorZychla:比喻你的电子邮件服务没有运行数据库服务器,这当然是你在单元测试你的代码时没有打扰的事情.伪造上下文和集合的问题是默默地替换Linq提供者.除非您使用真正的数据库提供程序测试代码,否则您根本没有测试过您的Linq查询.这就是重点.通过重构代码,你可以获得更好的分离,你的假将隐藏查询(不仅仅是设置或上下文),查询本身将单独测试. (3认同)
  • Ladislav - 你知道如何在所描述的情况下运行(和自动化)集成测试的任何好资源吗? (2认同)
  • 真?难道你不能想象一个模拟数据库会有用的案例吗?这些特定的测试不会告诉您LINQ-to-SQL错误,但是它们会测试其他错误. (2认同)
  • @heneryville:当然可以!但是,编写代码的方式不仅仅是在定义必须由集成测试覆盖的代码和应该用单元测试覆盖的代码之间的明确边界的方式吗?在这样的代码中,您不会伪造上下文或设置,因为它的用法将被另一个负责执行查询的类隐藏.该类将包含集成测试,其在其他代码中的使用将在单元测试中伪造. (2认同)

tam*_*asf 13

我正在开发一个开源库来解决这个问题.

http://effort.codeplex.com

一个小预告片:

您不必添加任何样板代码,只需调用库的相应API,例如:

var context = Effort.ObjectContextFactory.CreateTransient<MyContext>();
Run Code Online (Sandbox Code Playgroud)

起初这看起来很神奇,但创建的ObjectContext对象将与内存数据库通信,根本不会与原始真实数据库通信.术语"瞬态"是指该数据库的生命周期,它仅存在于创建的ObjectContext对象期间.并发创建的ObjectContext对象与专用数据库实例进行通信,数据不会与它们共享.这样可以轻松编写自动化测试.

该库提供了各种功能来自定义创建:跨实例共享数据,设置数据库的初始数据,在不同的数据层上创建假数据库...查看项目站点以获取更多信息.


jus*_*mer 12

从EF 4.3开始,您可以在创建上下文之前通过注入假来对代码进行单元测试DefaultConnectionFactory.


Jos*_*her 5

实体框架4.1几乎可以在测试中进行模拟,但需要额外的努力.T4模板为您提供包含DbSet属性的DbContext派生类.我认为你需要模拟的两件事是这些属性返回的DbSet对象以及你在DbContext派生类上使用的属性和方法.两者都可以通过修改T4模板来实现.

布伦特麦克德瑞克所示的类型的需要来进行修改的这个帖子,但没有能够实现这一目标的T4模板修改.粗略地说,这些是:

  1. 将DbContext派生类的DbSet属性转换为IDbSet属性.
  2. 添加一个为DbContext派生类生成接口的部分,该类包含IDbSet属性以及您需要模拟的任何其他方法(如SaveChanges).
  3. 在DbContext派生类中实现新接口.

  • 是的,对于您的示例,您将在单元测试和集成测试之间得到不一致的结果,并意识到您使用的技术不起作用.但是,如果您使用更多的vanilla LINQ来检索实体,然后使用它们执行某些业务逻辑,那么在单元测试中测试该业务逻辑仍然非常有效.例如,更改您的示例以分两步执行操作:1)通过LINQ(可能.ToList)检索项目,2)使用您的方法剥离列表.然后单元测试仍然有用,集成测试不必涵盖所有边缘情况. (10认同)
  • 但这正是错误和无用的.如果您模拟集合/上下文并使用我的答案中的示例,您将获得绿色单元测试,但在运行时会出现异常.它不会是数据库中的例外 - 它将是实体框架中的异常=您浪费时间编写测试,其中没有测试任何内容并且对您的代码一无所知.那么这种测试的价值是什么呢?它测试的是什么?此外,许多人遵循规则,你不应该模拟你不拥有的代码,因为在这种情况下你只是猜测它的功能,当你嘲笑它. (6认同)
  • 当然,您的方法在某些情况下有效,但单元测试策略必须适用于所有情况 - 为了使其工作,您必须完全从您的测试方法移动EF和`IQueryable`.这不是单元测试的假设您正确编写它 - 测试的关键是验证您是否正确编写了逻辑.同样,编写单元和集成测试以验证必须在您的方案中执行的相同代码并不是一个好习惯. (6认同)