我在寻求一些建议。我正在开发一个使用实体框架作为 Orm 的项目。我们始终使用代码优先的方法。我们也在使用行为驱动开发,并使用 specflow 和 selenium 创建了一组自动化 Web 测试。我需要能够从测试期间创建的数据库中删除所有数据。所以理想情况下,在我创建的在测试后执行的测试挂钩中,我想删除在测试期间添加的所有数据。
理想情况下,我想保持代码优先的方法,但我愿意接受建议。我想看看其他人如何提供解决方案并从他们那里获得一些建议。
我在寻求一些建议。我正在开发一个使用实体框架作为 Orm 的项目。我们始终使用代码优先的方法。我们也在使用行为驱动开发,并使用 specflow 和 selenium 创建了一组自动化 Web 测试。我需要能够从测试期间创建的数据库中删除所有数据。所以理想情况下,在我创建的在测试后执行的测试挂钩中,我想删除在测试期间添加的所有数据。
理想情况下,我想保持代码优先的方法,但我愿意接受建议。我想看看其他人如何提供解决方案并从他们那里获得一些建议。
不是特定的 SpecFlow 或任何其他测试框架,但我强烈建议使用内存数据库。
https://www.nuget.org/packages/Effort.EF6/
结合 DI 容器,您可以在测试中很好地控制数据库的生命周期。
LocalIocManager.IocContainer.Register(
Component.For<DbConnection>()
.UsingFactoryMethod(Effort.DbConnectionFactory.CreateTransient)
.LifestyleCustom<ManualLifestyleManager>());
您可以重复使用相同的 Seeder 来填充测试数据、转储或从 CSV 恢复等。
如果是 BDD,那么我相信您正在使用 Specflow。Specflow 提供了挂钩,您可以在其中放置拆卸方法。要收集您在测试执行期间创建的数据,您可以将其存储在Specflow 提供的 ScenarioContext 中。在拆解中,您可以访问它并读出您的数据并让 EF 删除它们。
我相信你有3个选择:
1,您有一个数据集最少的数据库,并且每个测试在背景部分都有自己的测试数据。当后台步骤将数据放入数据库时,您可以将此数据存储在 ScenarioContext 中,并在 AfterScenario 挂钩中删除这些记录。如果您对测试数据的所有权感到满意,那就太好了。这样,您可能拥有其他不受您控制的已创建数据。
2,解决方案 1,是在 AfterScenario 或 BeforeScenario 中运行一个脚本,该脚本清理并将某个数据集插入数据库。此解决方案的缺点是,删除/截断时间和填充时间可能对您来说太多了。
3、在AfterScenario或BeforeScenario,或者定期在测试执行期间可以恢复你的数据库。我相信这种方式取决于恢复数据库需要多少时间。
我相信您可以结合上述选项,因为这对您来说是可行的。您的解决方案取决于数据库类型、您使用的数据以及您拥有的环境。
您可以在 BeforeScenario 挂钩中备份数据库,然后在 AfterScenario 挂钩中恢复该备份。
最终经过调查和一些玩弄,我通过执行以下操作实现了这一点:
基于 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方法中,我检查数据库是否存在 - 如果存在,那么我执行执行以下操作的清理脚本:
这是该脚本的外观。
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 的优势,我们确保了以下几点: