实体框架迁移 - 如何创建单元测试以确保迁移模型是最新的?

问题描述 投票:0回答:5

我正在使用与 TeamCity、NUnit 和 Git 的持续集成。我最近从 FluentMigrator 迁移(请原谅双关语)到实体框架迁移。我这样做主要是为了利用其中的脚手架功能。

但是,有可能签入源代码管理的某些更改,而无需首先将更改搭建到迁移中(想象一下在提交和推送提交之前应用程序未运行的场景)。我正在使用预先测试的提交工作流程,所以我想在预测试中检测这个问题,而不是等到调用 migrate.exe (这在我的工作流程中为时已晚并破坏“绿色存储库”) .

我的问题是,如何创建单元/集成测试来检测迁移模型何时与上下文模型不匹配,以便我可以在尝试迁移之前使构建失败?请注意,我希望它与数据库不匹配,并且不希望从测试中访问数据库。

我尝试过这种方法:

    [Test]
    public void CheckWhetherEntityFrameworkMigrationsContextIsUpToDate()
    {
        Assert.DoesNotThrow(() =>
        {
            CallDatabase();
        });

    }

    private void CallDatabase()
    {
        using (var ctx = new MyContext("SERVER=(local);DATABASE=MyDatabase;Integrated Security=True;"))
        {
            var tenant = (from t in ctx.Tenant
                          select t).FirstOrDefault();
        }
    }

但是,当存在待处理的迁移时,这总是会失败(而不仅仅是在迁移模型与上下文模型不同步的情况下,这就是我所追求的)。

更新

我已在 EntityFramework 项目上针对此问题添加了一个 work item。希望他们会考虑添加一种方法来做到这一点。

entity-framework unit-testing continuous-integration database-migration entity-framework-migrations
5个回答
12
投票

如果有人发现这有用,我通过使用以下代码针对测试数据库运行所有迁移来测试迁移:

[TestClass]
public class MigrationsTests
{
    [TestMethod]
    public void RunAll()
    {
        var configuration = new Configuration();
        var migrator = new DbMigrator(configuration);
        // back to 0
        migrator.Update("0");
        // up to current
        migrator.Update();
        // back to 0
        migrator.Update("0");
    }
}

这会测试所有

Up
Down
迁移,并在自动迁移关闭时检测待处理的更改。

注意: 确保针对测试数据库运行此操作。在我的例子中,测试项目的

app.config
具有到测试数据库的连接字符串(只是一个本地 SQLExpress 实例)。


8
投票

这里有一些代码,用于检查所有模型更改是否已搭建到迁移中。

bool HasPendingModelChanges()
{
    // NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
    var migrationsConfiguration = new Migrations.Configuration();
    var migrator = new DbMigrator(migrationsConfiguration);
    var scriptingMigrator = new MigratorScriptingDecorator(migrator);

    try
    {
        // NOTE: Using InitialDatabase so history won't be read from the database
        scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
    }
    catch (AutomaticMigrationsDisabledException)
    {
        return true;
    }

    return false;
}

5
投票

我想要迄今为止这里给出的所有答案中最好的,所以这就是我想出的:

[TestClass()]
public class MigrationsTests
{
    [TestMethod()]
    public void MigrationsUpDownTest()
    {
        // Unit tests don't have a DataDirectory by default to store DB in
        AppDomain.CurrentDomain.SetData("DataDirectory", System.IO.Directory.GetCurrentDirectory());

        // Drop and recreate database
        BoxContext db = new BoxContext();
        db.Database.Delete();

        var configuration = new Migrations.Configuration();
        var migrator = new DbMigrator(configuration);

        // Retrieve migrations
        List<string> migrations = new List<string>;
        migrations.AddRange(migrator.GetLocalMigrations());

        try
        {
            for (int index = 0; index < migrations.Count; index++)
            {
                migrator.Update(migrations[index]);
                if (index > 0) {
                    migrator.Update(migrations[index - 1]);
                } else {
                    migrator.Update("0"); //special case to revert initial migration
                }
            }

            migrator.Update(migrations.Last());
        }
        catch (SqlException ex)
        {
            Assert.Fail("Should not have any errors when running migrations up and down: " + ex.Errors[0].Message.ToString());
        }

        // Optional: delete database
        db.Database.Delete();
    }

    [TestMethod()]
    public void PendingModelChangesTest()
    {
        // NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
        var migrationsConfiguration = new Migrations.Configuration();
        var migrator = new DbMigrator(migrationsConfiguration);
        var scriptingMigrator = new MigratorScriptingDecorator(migrator);

        try
        {
            // NOTE: Using InitialDatabase so history won't be read from the database
            scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
        }
        catch (AutomaticMigrationsDisabledException)
        {
            Assert.Fail("Should be no pending model changes/migrations should cover all model changes.");
        }
    }
}

有几件事值得注意:

  • 您需要在测试项目中安装Entity Framework
  • 您需要将
    [assembly: InternalsVisibleTo("MyTestsProject")]
    添加到
    Configuration
    类的顶部
  • 您需要在
    App.config
    中指定一个仅用于测试项目的实体框架连接字符串,因为数据库将被频繁删除和重新创建 - 如果在构建计算机上运行,请记住并行运行可能会导致冲突,因此您可能需要更改每个构建的字符串

3
投票

我认为这比 Pablo Romeo 的代码效果更好。此操作会升级,然后再次降级,以捕获降级脚本中丢失的项目。

[TestFixture]
class MigrationTest
{
    [Test]
    public void RunAll()
    {
        var configuration = new Configuration();
        var migrator = new DbMigrator(configuration);

        // Retrieve migrations
        List<string> migrations = new List<string> {"0"};  // Not sure if "0" is more zero than the first item in list of local migrations
        migrations.AddRange(migrator.GetLocalMigrations());

        migrator.Update(migrations.First());

        // Doe een stapje naar voren, en een stapje terug (www.youtube.com/watch?v=2sg1KAxuWKI)
        // (Dutch pun) meaning: take a small step forward, and a small step back ;)
        for (int index = 0; index < migrations.Count; index++)
        {
            migrator.Update(migrations[index]);
            if (index > 0)
                migrator.Update(migrations[index - 1]);
        }

        migrator.Update(migrations.Last());

        migrator.Update(migrations.First());
    }
}

1
投票

EF Core 有一个非常简单的方法:

myDbContext.Database.Migrate();

或者您可以像这样检查待处理的迁移:

var migrator = myDbContext.GetInfrastructure().GetService<IMigrator>();

var migrations = myDbContext.Database.GetPendingMigrations();

foreach (var migration in migrations)
{
     migrator.Migrate(migration);
}
© www.soinside.com 2019 - 2024. All rights reserved.