1

我在寻求一些建议。我正在开发一个使用实体框架作为 Orm 的项目。我们始终使用代码优先的方法。我们也在使用行为驱动开发,并使用 specflow 和 selenium 创建了一组自动化 Web 测试。我需要能够从测试期间创建的数据库中删除所有数据。所以理想情况下,在我创建的在测试后执行的测试挂钩中,我想删除在测试期间添加的所有数据。

理想情况下,我想保持代码优先的方法,但我愿意接受建议。我想看看其他人如何提供解决方案并从他们那里获得一些建议。

4

4 回答 4

1

不是特定的 SpecFlow 或任何其他测试框架,但我强烈建议使用内存数据库。

https://www.nuget.org/packages/Effort.EF6/

结合 DI 容器,您可以在测试中很好地控制数据库的生命周期。

LocalIocManager.IocContainer.Register(
    Component.For<DbConnection>()
    .UsingFactoryMethod(Effort.DbConnectionFactory.CreateTransient)
    .LifestyleCustom<ManualLifestyleManager>());

您可以重复使用相同的 Seeder 来填充测试数据、转储或从 CSV 恢复等。

于 2016-11-28T15:24:12.560 回答
0

如果是 BDD,那么我相信您正在使用 Specflow。Specflow 提供了挂钩,您可以在其中放置拆卸方法。要收集您在测试执行期间创建的数据,您可以将其存储在Specflow 提供的 ScenarioContext 中在拆解中,您可以访问它并读出您的数据并让 EF 删除它们。

我相信你有3个选择:

1,您有一个数据集最少的数据库,并且每个测试在背景部分都有自己的测试数据。当后台步骤将数据放入数据库时​​,您可以将此数据存储在 ScenarioContext 中,并在 AfterScenario 挂钩中删除这些记录。如果您对测试数据的所有权感到满意,那就太好了。这样,您可能拥有其他不受您控制的已创建数据。

2,解决方案 1,是在 AfterScenario 或 BeforeScenario 中运行一个脚本,该脚本清理并将某个数据集插入数据库。此解决方案的缺点是,删除/截断时间和填充时间可能对您来说太多了。

3、在AfterScenario或BeforeScenario,或者定期在测试执行期间可以恢复你的数据库。我相信这种方式取决于恢复数据库需要多少时间。

我相信您可以结合上述选项,因为这对您来说是可行的。您的解决方案取决于数据库类型、您使用的数据以及您拥有的环境。

于 2016-11-26T11:25:42.630 回答
0

您可以在 BeforeScenario 挂钩中备份数据库,然后在 AfterScenario 挂钩中恢复该备份。

于 2016-11-27T15:17:44.017 回答
0

最终经过调查和一些玩弄,我通过执行以下操作实现了这一点:

基于 CreateDatabaseIfNotExists ( http://www.entityframeworktutorial.net/code-first/database-initialization-strategy-in-code-first.aspx ) 创建了一个实体框架初始化器:

public class DbTestInitializer : CreateDatabaseIfNotExists<DBContext>
{
    public override void InitializeDatabase(DBContext context)
    {
        if(context.Database.Exists())
        {
            // We have a database already, so we can clean entities and run seed.
            CleanseTables(context);
            Seed(context);
        }

        base.InitializeDatabase(context);
    }

    private void CleanseTables(DBContext context)
    {
        // Run the database teardown script 
        context.Database.ExecuteSqlCommand(Properties.Resources.DatabaseTeardown);
    }

    protected override void Seed(DBContext context)
    {
        this.ApplySeed(context);
    }
}

在初始化程序InitializeDatabase方法中,我检查数据库是否存在 - 如果存在,那么我执行执行以下操作的清理脚本:

  • 收集所有外键引用并构造一个删除约束并为每个外键创建约束脚本。然后我执行了 drop constraints,截断了数据库中的每个表(不包括特定于 EF Migrations 的 MigrationHistory 表),并使用创建约束脚本重新启用了约束。这是通过context.Database.ExecuteSQLCommand([script])执行脚本来完成的

这是该脚本的外观。

DECLARE @ConstraintsTable TABLE
(

ID INT IDENTITY(1,1),

DropConstraintScript VARCHAR(MAX),

EnableConstraintScript VARCHAR(MAX)

)

INSERT INTO @ConstraintsTable
SELECT 
'ALTER TABLE [' + ForeignKeys.ForeignTableSchema
            + '].[' + ForeignKeys.ForeignTableName + '] DROP CONSTRAINT     ['
            + ForeignKeys.ForeignKeyName + ']; ',
    'ALTER TABLE [' + ForeignKeys.ForeignTableSchema
            + '].[' + ForeignKeys.ForeignTableName
            + '] WITH CHECK ADD CONSTRAINT [' + ForeignKeys.ForeignKeyName
            + '] FOREIGN KEY([' + ForeignKeys.ForeignTableColumn
            + ']) REFERENCES [' + SCHEMA_NAME(sys.objects.schema_id)
            + '].[' + sys.objects.[name] + ']([' + sys.columns.[name]
            + ']);'
    FROM    sys.objects
    INNER JOIN sys.columns
            ON ( sys.columns.[object_id] = sys.objects.[object_id] )
    INNER JOIN ( SELECT sys.foreign_keys.[name] AS ForeignKeyName
                        ,SCHEMA_NAME(sys.objects.schema_id) AS ForeignTableSchema
                        ,sys.objects.[name] AS ForeignTableName
                        ,sys.columns.[name] AS ForeignTableColumn
                        ,sys.foreign_keys.referenced_object_id AS referenced_object_id
                        ,sys.foreign_key_columns.referenced_column_id AS referenced_column_id
                    FROM   sys.foreign_keys
                    INNER JOIN sys.foreign_key_columns
                        ON ( sys.foreign_key_columns.constraint_object_id = sys.foreign_keys.[object_id] )
                    INNER JOIN sys.objects
                        ON ( sys.objects.[object_id] = sys.foreign_keys.parent_object_id )
                    INNER JOIN sys.columns
                        ON ( sys.columns.[object_id] = sys.objects.[object_id] )
                            AND ( sys.columns.column_id = sys.foreign_key_columns.parent_column_id )
                ) ForeignKeys
            ON ( ForeignKeys.referenced_object_id = sys.objects.[object_id] )
                AND ( ForeignKeys.referenced_column_id = sys.columns.column_id )
    WHERE   ( sys.objects.[type] = 'U' )
    AND ( sys.objects.[name] NOT IN ( 'sysdiagrams' ) )

    declare @count int, @ndx int
    declare @script nvarchar(max)
    select @count = count(ID) from @ConstraintsTable
    set @ndx = 1

    while(@ndx <= @count)
    begin
         select @script = DropConstraintScript from @ConstraintsTable where ID = @ndx
         EXEC sp_executesql @script;

         set @ndx = @ndx + 1
    end

    EXEC sp_msforeachtable @command1 = 'TRUNCATE TABLE ?', @whereand = 'AND Object_Id NOT IN (SELECT Object_Id FROM sys.objects WHERE name like ''__Migration%'')';

    set @ndx = 1

    while(@ndx <= @count)
    begin
         select @script = EnableConstraintScript from @ConstraintsTable where ID = @ndx
         EXEC sp_executesql @script;

         set @ndx = @ndx + 1
    end

剧本

EXEC sp_msforeachtable @command1 = 'TRUNCATE TABLE ?', @whereand = 'AND Object_Id NOT IN (SELECT Object_Id FROM sys.objects WHERE name like ''__Migration%'')';

截断数据库中除迁移表之外的所有表。这是执行了放置约束脚本之后。在表被截断后,脚本的下一部分将所有约束添加回。通过使用表变量而不是游标,我们应该在脚本执行时获得更好的性能。有可能在某些地方改进脚本以获得更好的性能。这是我们依赖数据库脚本执行的唯一领域。通过利用 EF Initializer 的优势,我们确保了以下几点:

  • 如果尚未创建数据库,则可以从头开始创建数据库。
  • 使用上面在CleanseTables方法中详述的脚本来清理数据库(所有表都被截断)。
  • 然后为数据库播种。在我的实例中,我对测试数据库初始化程序使用了与默认迁移初始化程序相同的种子。这可确保数据库“重置”为添加任何数据之前数据库的状态。
于 2016-11-30T02:32:17.037 回答