2016-11-26 2 views
1

Я после некоторого совета. Я работаю над проектом, использующим Entity Framework в качестве Orm. Мы используем кодовый подход во всем. Мы также используем разработку, основанную на поведении, и создали набор автоматизированных веб-тестов с использованием specflow и selenium. Мне нужно иметь возможность удалять все данные из базы данных, которая была создана во время теста. Поэтому в идеальном случае, в тестовом крючке, который я создал, который запускается после теста, я хочу удалить все данные, которые были добавлены во время теста.Снятие данных с использованием структуры Entity Framework

В идеале я хотел бы придерживаться первого подхода кода, но я открыт для предложений. Я хотел бы посмотреть, как другие предоставили решение и получить от них некоторые советы.

ответ

0

Если BDD, то я считаю, что вы используете Specflow. Specflow обеспечивает hooks, где вы можете использовать свои методы срыва. Чтобы собрать данные, созданные во время выполнения теста, вы можете сохранить его в ScenarionContext, предоставленном также Specflow. В режиме разрыва вы можете получить к нему доступ и зачитать свои данные и позволить EF удалить их.

Я считаю, что у вас есть 3 варианта:

1, у вас есть база данных с минимальным набором данных и каждый тест имеет свои собственные тестовые данные в разделе Background. Когда фоновый шаг помещает данные в db, вы можете сохранить эти данные в своем сценарии ScenarioContext, а в hook после Afterccenario вы можете удалить эти записи. Хорошо, если вы удовлетворены владением над вашими тестовыми данными. Таким образом, у вас могут быть другие созданные данные, которые не находятся под вашим контролем.

2, Решение для 1 - это то, что в AfterScenario или в BeforeScenario запускается скрипт, который очищает и вставляет определенный набор данных в базу данных. Недостатком этого решения является то, что время удаления и усечения и время заполнения могут быть для вас слишком большими.

3, В AfterScenario или BeforeScenario или периодически во время выполнения теста вы можете восстановить свою базу данных. Я считаю, что этот способ зависит от того, сколько времени требуется для восстановления вашего db.

Я считаю, что вы можете комбинировать приведенные выше варианты, как это возможно для вас. Ваше решение зависит от типа базы данных, данных, с которыми вы работаете, и от условий, которые у вас есть.

+0

Да, я уже использую этот подход, и уже есть случаи, когда я управляю данными, управляется в тестах. Однако в результате автоматизированного веб-тестирования также будут данные, которые вставляются/корректируются во время теста, который нельзя контролировать. То, что я ищу, - это решение «сбросить» базу данных до состояния, которое было до запуска теста. –

+0

Почему бы не обрезать таблицы и вставить в нее исходные данные? Это часть управления тестовыми данными. Вы должны контролировать контрольные данные. Храните его в файле сценария sql и запускайте его при каждом тестировании или когда это необходимо. – SayusiAndo

+0

Я реализовал это, выполнив следующее: добавлен EF-инициализатор, специфичный только для моего набора тестов. Он основан на инициализаторе CreateDatabaseIfNotExists, поэтому, если база данных не существует, она будет создана.Если он существует, то я включил, чтобы все объекты были извлечены и усечены, а затем сеяние снова запускается. Я играл, оставив его просто удалить и создать базу данных, которая отлично работает. Проблема в том, что по мере того, как тесты начинают расти, а данные становятся более экспансивными, производительность инициализатора при отбрасывании и воссоздании базы данных будет крайне низкой. –

0

Вы можете сделать резервную копию БД в крючке BeforeScenario, а затем восстановить эту резервную копию в крючке AfterScenario.

1

Не определен SpecFlow или любые другие рамки тестирования, но я бы очень рекомендовал использовать базу данных в памяти.

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

В сочетании с DI contaner вы получите очень хороший контроль срока службы базы данных в тестах.

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

Вы можете использовать такие же Сеялки для заполнения данных испытаний, сброса или восстановления из CSV и т. Д.

+0

Теперь это определенно то, что я буду рассматривать в будущем. Это не подходит для моего текущего проекта, но определенно то, что я хотел бы использовать. Спасибо за комментарий. Очень признателен. –

0

В конце концов, после расследования и немного играть вокруг, я достиг этого, выполнив следующие действия:

создал Entity Framework Initializer на основе 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, я проверяю, существует ли база данных - если это так, я выполняю сценарий очистки, который выполняет следующие действия:

  • Собрано все ссылки на внешние ключи и построено ограничение на отказ и создать ограничение скрипт для каждого из них. Затем я выполнил ограничения на падение, усеченные каждую таблицу в базе данных (за исключением таблицы MigrationHistory, специфичной для EF Migrations), и повторно связали ограничения с помощью сценариев create. Это делается путем выполнения сценария с помощью context.Database.ExecuteSQLCommand ([сценарий])

Вот как этот сценарий выглядит.

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.
  • База данных затем высевается. В моем экземпляре я использовал тот же самый посев для инициализатора тестового db, который у меня есть для инициализатора миграции по умолчанию. Это гарантирует, что база данных будет «сброшена» до состояния, в котором база данных была до того, как были добавлены какие-либо данные.
Смежные вопросы