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

Posted

技术标签:

【中文标题】集成测试共享数据库的多个实体框架 dbcontexts【英文标题】:Integration testing multiple Entity framework dbcontexts that share a database 【发布时间】:2014-12-03 03:22:54 【问题描述】:

在我的应用程序中,我有多个共享同一个数据库的小型实体框架 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 - 为集成测试创建一个自定义初始化程序,它将运行所有适当的数据库初始化脚本:

public class IntegrationTestInitializer : IDatabaseInitializer<DbContext> 

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

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

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

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

我目前倾向于选项 2。你们觉得呢?有没有更好的方法?

【问题讨论】:

【参考方案1】:

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

我最终得到的是选项 4:通过常规用户界面维护单元测试数据库内容。当然,大多数集成测试临时会修改数据库内容,作为测试“执行”阶段的一部分(稍后会详细介绍这个“临时”),但内容是测试会话开始时未设置。

这就是原因。

在某个阶段,我们还在测试会话开始时通过代码或反序列化 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 是一个静态成员变量。

【讨论】:

感谢您抽出宝贵时间回答这个问题。您概述的测试的事务方面肯定很有用,我现在正在做的事情。【参考方案2】:

选项 2 或其任何运行实际数据库更新脚本的变体将是最好的。否则,您不一定要针对生产中的同一数据库进行集成测试(至少就架构而言)。

为了解决您对每次添加新数据库脚本时都需要更新的担忧,如果您要将所有脚本保存在一个文件夹中,也许在项目中使用“如果更新则复制”的构建操作,您可以以编程方式读取每个文件并执行其中的脚本。只要您从中读取文件的地方是更新脚本的规范存储库,您就无需进入并进行任何进一步的更改。

【讨论】:

虽然我确实喜欢读取每个文件的想法,这样我就不必更新初始化程序,唯一的问题是脚本通常需要按特定顺序运行。例如,如果脚本 1 创建了一个表,但脚本 2 向该表添加了一个字段并且脚本 2 首先运行,那么它将失败。不过我敢肯定有一个解决方法 在文件名前加上 ISO 8601 格式的日期时间戳 是的,这将是一个明智的做法 - 不知道为什么我没有想到这一点,但谢谢 :)

以上是关于集成测试共享数据库的多个实体框架 dbcontexts的主要内容,如果未能解决你的问题,请参考以下文章

实体框架,多个 edmx 共享连接字符串 - 可能吗?

实体框架 [Key] 标签未被识别

实体框架无法删除数据库,数据库正在使用中

实体框架、SQL CE 4.0 和 DB 测试自动化

集成测试框架

实体框架的悲观并发