单元测试DbContext

时间:2011-07-20 18:14:07

标签: unit-testing entity-framework-4.1

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

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

但是,我试图避免使用存储库来抽象EF作为pointed by some人,因为EF 4.1已经通过DbSet和DbContext实现了存储库和工作单元模式,我真的希望保留EF团队实现的所有功能,而不必使用通用存储库来维护它们,就像我在其他项目中所做的那样(这有点痛苦)。

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

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

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

5 个答案:

答案 0 :(得分:35)

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

你提到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();
        }
    }
}

现在假设你在SUT中有这个(被测系统 - 在单元测试的情况下它是一个单位=通常是一个方法)。在测试代​​码中,您提供FakeContextFakeSet并且它将起作用 - 您将进行绿色测试。现在,在生产代码中,您将提供另一个派生的DbContextDbSet,并且您将在运行时获得异常。

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

答案 1 :(得分:13)

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

http://effort.codeplex.com

一个小预告:

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

var context = Effort.ObjectContextFactory.CreateTransient<MyContext>();

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

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

答案 2 :(得分:12)

从EF 4.3开始,您可以unit test your code在创建上下文之前注入假DefaultConnectionFactory

答案 3 :(得分:5)

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

Brent McKendrick已经展示了需要在this post中进行的修改类型,而不是可以实现此目的的T4模板修改。粗略地说,这些是:

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

答案 4 :(得分:1)

对于仍在寻找答案的人 - 我写了一个简单的库,以便于以非常简单的方式模拟DbContext。有关详细信息,请参阅我对类似问题的其他答案:https://stackoverflow.com/a/33716219/111438

PS - 我同意Ladislav Mrnka的观点,但我认为在某些情况下,你需要模拟你的DbSet并对其进行单元测试是不可避免的。虽然您需要记住,您不应该测试LINQ查询以验证它们是否返回正确的数据。这就是集成测试更适用的地方。