集成测试共享数据库的多个Entity框架dbcontexts

时间:2014-10-08 12:51:46

标签: c# entity-framework

在我的应用程序中,我有多个小实体框架dbcontexts,它们共享同一个数据库,例如:

public class Context1 : DbContext {
    public Context1()
        : base("DemoDb") {
    }
}

public class Context2 : DbContext {
    public Context2()
        : base("DemoDb") {
    }
}

所有数据库更新都是通过脚本完成的,不依赖于迁移(也不会继续)。问题是 - 你将如何针对这些背景进行集成测试?

我相信这里有三个选项(可能有更多我不知道)

选项1 - 超级上下文 - 包含设置数据库所需的所有模型和配置的上下文:

public class SuperContext : DbContext
{
    public SuperContext()
        : base("DemoDb") {
    }
}

在此选项中,将针对超级上下文设置测试数据库,并且所有后续测试将通过较小的上下文完成。 我不喜欢这个选项的原因是我将复制我已经构建的所有配置和实体模型。

选项2 - 为集成测试创建自定义初始化程序,以运行所有相应的db初始化脚本:

public class IntegrationTestInitializer : IDatabaseInitializer<DbContext> {

    public void InitializeDatabase(DbContext context) {
        /* run scripts to set up database here */
    }
}

此选项允许针对真正的数据库结构进行测试,但每次添加新的数据库脚本时也需要更新

选项3 - 只测试各个上下文:

在此选项中,只需让EF根据上下文创建测试数据库,所有测试都将在自己的“沙箱”中运行。 我之所以不喜欢这样,是因为你不会对数据库的真实表示进行测试。

我目前正在选择2.你们都有什么想法?那里有更好的方法吗?

2 个答案:

答案 0 :(得分:5)

我经常使用集成测试,因为我仍然认为它是涉及数据相关流程时最可靠的测试方式。我还有几个不同的上下文和用于数据库升级的DDL脚本,因此我们的情况非常相似。

我最终得到的是选项4 :通过常规用户界面维护单元测试数据库内容。当然,大多数集成测试暂时修改数据库内容,作为&#34; act&#34;的一部分。测试阶段(更多关于此&#34; 临时&#34;稍后),但测试会话开始时未设置内容。

这就是原因。

在某个阶段,我们还在测试会话开始时通过代码或反序列化XML文件生成数据库内容。 (我们还没有EF,但是否则我们可能在数据库初始化程序中有一些Seed方法)。渐渐地,我开始对这种方法感到疑虑。当数据模型或业务逻辑发生变化时,维护代码/ XML是一项艰巨的任务,尤其是何时必须设计新的用例。有时我允许自己对这些测试数据进行轻微破坏,因为它知道它不会影响测试。

此外,数据必须有意义,因为它们必须与来自实际应用程序的数据一样有效和连贯。确保这一点的一种方法是由应用程序本身生成数据,否则您将不可避免地以某种方式在种子方法中复制业务逻辑。 模拟真实世界的数据实际上非常很难。这是我发现的最重要的事情。测试不能代表真实用例的数据星座不仅仅是浪费时间,而且是虚假安全。

所以我发现自己通过应用程序的前端创建测试数据,然后将这些内容精心地序列化为XML或编写生成完全相同的代码。直到有一天,我发现我在这个数据库中有数据,所以为什么不直接使用它?

现在也许你问如何让测试独立?

集成测试,就像单元测试一样,应该可以单独执行。他们不应该依赖其他测试,也不应该受他们的影响。我假设您的问题的背景是您为每个集成测试创建和播种数据库。这是实现独立测试的一种方式。

但是,如果只有一个数据库,并且没有种子脚本呢?您可以为每个测试恢复备份。我们选择了不同的方法。每个集成测试都在TransactionScope内运行,但从未提交过。实现这一目标非常容易。每个测试夹具都继承自具有这些方法的基类(NUnit):

[SetUp]
public void InitTestEnvironment()
{
    SetupTeardown.PerTestSetup();
}

[TearDown]
public void CleanTestEnvironment()
{
    SetupTeardown.PerTestTearDown();
}

SetupTeardown

public static void PerTestSetup()
{
    _tranactionScope = new TransactionScope();
}

public static void PerTestTearDown()
{
    if (_tranactionScope != null)
    {
        _tranactionScope.Dispose(); // Rollback any changes made in a test.
        _tranactionScope = null;
    }
}

其中_tranactionScope是静态成员变量。

答案 1 :(得分:0)

选项2或运行实际数据库更新脚本的任何变体都是最好的。除此之外,您不一定要对生产中的相同数据库进行集成测试(至少相对于模式)。

为了解决每次添加新数据库脚本时需要更新的问题,如果要将所有脚本保存在单个文件夹中,可能在项目中使用“copy if newer”构建操作,则可以以编程方式读取每个文件并在其中执行脚本。只要您从中读取文件的位置是更新脚本的规范存储库,您就永远不需要进入并进行任何进一步的更改。