我应该如何设置我的集成测试以使用带有实体框架的测试数据库?

Posted

技术标签:

【中文标题】我应该如何设置我的集成测试以使用带有实体框架的测试数据库?【英文标题】:How should I set up my integration tests to use a test database with Entity Framework? 【发布时间】:2012-12-01 04:27:06 【问题描述】:

我正在为应用程序编写集成测试,但找不到任何关于如何为我的集成套件设置测试数据库的最佳实践。我正在使用实体框架代码优先的 ASP.NET MVC4 应用程序。

我可以确认我的测试项目中的测试默认与我机器上的本地开发数据库通信。这并不理想,因为我希望每次运行测试时都有一个新的数据库。

如何设置我的测试项目,以便我的测试与单独的实例通信?我假设可以设置 SQL Server Compact Edition 实例,但我不确定如何配置它。

【问题讨论】:

Jimmy Bogard 的这篇文章非常适合阅读该主题:Isolating database data in integration tests。 【参考方案1】:

非常感谢@Justin 和@Petro 的回答,它们对我帮助很大。我提出的解决方案是您建议的技术的组合。下面描述的解决方案为每次运行测试提供了一个新数据库,并为每个测试提供了一个单独的事务。

我在我的测试项目的 App.config 中为我的测试数据库添加了一个连接字符串:

  <connectionStrings>
    <add name ="TestDatabase"
     providerName="System.Data.SqlClient"
     connectionString="Data Source=(LocalDb)\v11.0;Database=TestDatabase;Integrated Security=True"/>
  </connectionStrings>

我为我的集成测试创建了一个基类,以提供设置和拆卸。安装程序实例化上下文,如果数据库尚不存在则创建数据库并启动事务。 Teardown 回滚事务。

public class EntityFrameworkIntegrationTest

    protected MyDbContext DbContext;

    protected TransactionScope TransactionScope;

    [TestInitialize]
    public void TestSetup()
    
        DbContext = new MyDbContext(TestInit.TestDatabaseName);
        DbContext.Database.CreateIfNotExists();
        TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
    

    [TestCleanup]
    public void TestCleanup()
    
        TransactionScope.Dispose();
    

最后,我有一个类负责在所有测试运行后删除数据库:

[TestClass]
public static class TestInit

    // Maps to connection string in App.config
    public const string TestDatabaseName = "TestDatabase";

    [AssemblyCleanup]
    public static void AssemblyCleanup()
    
        Database.Delete(TestDatabaseName);
    

我应该补充一点,我发现 this blog post about Entity Framework 对于更深入地了解实体框架在幕后/按照惯例所做的事情很有用。

【讨论】:

我会把 DbContext.Database.CreateIfNotExists();在带有 [AssemblyInitialize] 属性的方法中 如果我将名称传递给我的 DbContext 的构造函数,则不会发生任何事情。它仍然使用数据库的默认配置。为什么这对你有用而不是我?很奇怪。 MyDbContext 类在哪里?那是什么样子的? @Sonofblip 已经有一段时间了,但我怀疑我可能已经创建了 DbContext 的子类,因为我需要覆盖一些行为。如果只使用 DbContext 是否有效? @Kleky 模拟对单元测试很有意义——你可能会遇到其中的几个。不过,这个问题是关于编写集成测试的,您还需要进行一些测试,以确保您的代码也适用于真实的数据库。【参考方案2】:

只需在您的单元测试项目的 app.config 中设置一个指向新数据库实例的连接字符串。

然后您可以使用测试类中的初始化和清理方法来创建和删除数据库。

连接字符串只是普通的,例如

<add name="UnitTestDBConnection" connectionString="Data Source=(local);Initial Catalog=UnitTestDB;Integrated Security=SSPI;" providerName="System.Data.SqlClient"/>

然后要创建数据库,每次测试一次,您可以这样做:

    YourContext _ctx;

    [TestInitialize]
    public  void Initiaslise()
    

        YourNameDbInitialise initialiser = new YourNameDbInitialiseForTest();
        Database.SetInitializer(initialiser);

        _ctx = new YourNameContext();

        initialiser.InitializeDatabase(_ctx);         
    

在每次测试结束时删除

    [TestCleanup]
    public  void Cleanup()
    
        Database.Delete("YourName");
    

【讨论】:

谢谢。您能否在答案中分享一些代码来说明这是如何完成的?如何创建新数据库以及如何确保提供正确的连接字符串? 谢谢。在 OP 的原始问题中,他在protected MyDbContext DbContext; 的基类中做什么?这应该引用他的新集成测试数据库吗?显然MyDbContext 在我的项目中引发了一个智能感知错误 - 我为 localdb 添加了一个连接字符串,但不知道这与声明 DbContext 有何关系。 没错,就是指他的测试数据库。【参考方案3】:

如果您使用 NUnit,您可以使用 Setup/Teardown 属性和 TransactionScope 来不将您的更改提交到数据库:

[SetUp]
public void SetUp()

    transaction = new TransactionScope();


[TearDown]
public void TearDown()

    if(transaction != null) 
       transaction.Dispose();

如果您使用其他一些单元测试框架,它应该具有类似的属性。我建议为您的所有集成测试装置创建一个基类DbItegrationTest,因此如果您从此类派生,所有测试方法都不会提交到数据库。

要为其他数据库配置实体框架,请在您的测试程序集中覆盖 db 连接字符串。

【讨论】:

我更愿意为我的测试使用完全不同的数据库。使用相同的数据库意味着它可能包含种子数据,并且如果应用程序在测试运行时出于某种原因正在使用,它可能会发生变化。使用单独的数据库还可以测试我的迁移是否正常工作。 您可以使用其他数据库进行测试。为此,只需更新测试程序集的 app.config 中的连接字符串。使用与 MVC 应用程序中相同的连接字符串名称,但更新数据库名称/凭据等。如果您的测试程序集没有 app.config,您可以手动添加它。

以上是关于我应该如何设置我的集成测试以使用带有实体框架的测试数据库?的主要内容,如果未能解决你的问题,请参考以下文章

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

如何最好地处理带有嵌入式数据库的 Flyway 以进行集成测试?

带有 Autofac 单元测试 DataContext 的实体框架 6

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

.NET Core 的 NUnit Entity Framework 集成测试依赖注入问题

如何使用实体框架测试视图?