2016-11-26 5 views
1

Je suis après quelques conseils. Je travaille sur un projet utilisant Entity Framework comme Orm. Nous utilisons une approche basée sur le code tout au long. Nous utilisons également le développement piloté par le comportement et avons créé un ensemble de tests Web automatisés utilisant specflow et sélénium. Je dois pouvoir supprimer toutes les données de la base de données créée lors d'un test. Donc idéalement, dans le hook de test que j'ai créé qui est exécuté après un test, je veux supprimer toutes les données qui ont été ajoutées pendant le test.Démontage de données en utilisant Entity Framework

Idéalement, je voudrais garder la première approche du code, mais je suis ouvert aux suggestions. J'aimerais voir comment les autres ont fourni une solution et obtenir des conseils de leur part.

Répondre

0

Si BDD, alors je crois que vous utilisez Specflow. Specflow fournit hooks où vous pouvez placer vos méthodes de démontage. Pour collecter les données que vous avez créées pendant l'exécution du test, vous pouvez les stocker dans le ScenarionContext fourni par Specflow. Dans le démontage, vous pouvez y accéder et lire vos données et laisser EF les supprimer.

Je crois que vous avez 3 options:

1, Vous avez une base de données avec un minimum de jeu de données et tous les tests ont ses propres données de test dans la section arrière-plan. Lorsque l'arrière-plan place des données dans db, vous pouvez stocker ces données dans votre ScenarioContext et dans le hook AfterScenario vous pouvez supprimer ces enregistrements. C'est bien si vous êtes satisfait de la propriété de vos données de test. De cette façon, vous pourriez avoir d'autres données créées qui ne sont pas sous votre contrôle.

2, La solution pour le 1, est que dans AfterScenario ou dans BeforeScenario vous exécutez un script qui nettoie et insère un certain ensemble de données dans la base de données. L'inconvénient de cette solution est que le temps de suppression/tronquer et de remplissage peut être trop important pour vous. 3, Dans AfterScenario ou BeforeScenario, ou périodiquement pendant l'exécution du test, vous pouvez restaurer votre base de données. Je crois que cette façon dépend de combien de temps est nécessaire pour restaurer votre DB.

Je crois que vous pouvez combiner les options ci-dessus, car cela est faisable pour vous. Votre solution dépend du type de base de données, des données avec lesquelles vous travaillez et des environnements dont vous disposez.

+0

Oui, j'utilise déjà cette approche et il existe déjà des cas où je gère des données a été géré dans les tests. Cependant, à la suite des tests automatisés sur le Web, il y aura également des données qui sont insérées/modifiées au cours d'un test qui ne peut pas être contrôlé. Ce que je cherche, c'est une solution pour 'réinitialiser' la base de données à l'état dans lequel elle était avant l'exécution du test. –

+0

Pourquoi ne pas tronquer les tables et y insérer les données initiales? Cela fait partie de la gestion des données de test. Vous devez avoir le contrôle sur les données de test. Stockez-le dans un fichier script sql et exécutez-le à chaque test ou lorsque cela est nécessaire. – SayusiAndo

+0

Je l'ai implémenté en procédant comme suit: - Ajout d'un initialiseur EF spécifique à ma suite de tests uniquement. Il est basé sur l'initialiseur CreateDatabaseIfNotExists, donc si la base de données n'existe pas, elle sera créée.Si elle existe, alors j'ai activé de sorte que toutes les entités sont récupérées et tronquées, puis l'ensemencement s'exécute à nouveau. J'ai joué avec le laisser juste pour supprimer et créer la base de données, qui fonctionne très bien. Le problème est que lorsque les tests commencent à se développer et que les données deviennent plus volumineuses, les performances de l'initialiseur lors de la suppression et de la recréation de la base de données sont extrêmement médiocres. –

0

Vous pouvez effectuer une sauvegarde de la base de données dans le hook BeforeScenario, puis restaurer cette sauvegarde dans le hook AfterScenario.

1

Non spécifique SpecFlow ou tout autre cadre de test, mais je recommande fortement d'utiliser la base de données en mémoire.

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

En combinaison avec DI contaner vous obtenez un très bon contrôle de la durée de vie de base de données dans les tests.

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

Vous pouvez réutiliser les mêmes semoirs pour remplir des données de test, les sauvegarder ou les restaurer à partir de CSV, etc.

+0

Maintenant, c'est certainement quelque chose que je considérerais pour l'avenir. Ce n'est pas adapté à mon projet actuel mais certainement quelque chose que je voudrais utiliser. Merci pour le commentaire. Très appréciée. –

0

Finalement, après enquête et un peu de jeu autour, j'ai réalisé cela en procédant comme suit:

Création d'un Entity Framework Initializer basé sur 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); 
    } 
} 

A l'intérieur du initialiseur InitializeDatabase méthode, je vérifie pour voir si la base de données existe - si oui, alors j'exécute un script de nettoyage qui fait ce qui suit:

  • Collecté toutes les références de clés étrangères et construit une contrainte de dépôt et créer un script de contrainte pour chacun. J'ai ensuite exécuté les contraintes de suppression, tronqué toutes les tables de la base de données (à l'exclusion de la table MigrationHistory spécifique aux migrations EF) et réactivé les contraintes à l'aide des scripts create constraint. Cela se fait en exécutant le script via context.Database.ExecuteSQLCommand ([scénario])

Voilà comment ce script ressemble.

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 

Le script

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

tronque toutes les tables de la base de données, sauf pour la table de migration. C'est après les scripts de contrainte de dépôt ont été exécutés. Après que les tables sont tronquées, la partie suivante du script ajoute toutes les contraintes. En utilisant des variables de table au lieu de curseurs, nous devrions obtenir de meilleures performances lorsque le script s'exécute. Il peut être possible d'améliorer le script dans des endroits pour obtenir de meilleures performances. C'est la seule zone où nous comptons sur l'exécution de scripts de base de données. En utilisant les avantages de l'EF Initializer, nous assurons ce qui suit:

  • La base de données peut être créée à partir de zéro si elle n'est pas déjà créée.
  • La base de données est nettoyée (toutes les tables sont tronquées) en utilisant le script détaillé ci-dessus dans la méthode CleanseTables.
  • La base de données est ensuite ensemencée. Dans mon exemple, j'ai utilisé le même amorçage pour l'initialiseur db de test que j'ai pour mon initialiseur de migration par défaut. Cela garantit que la base de données est «réinitialisée» à l'état où se trouvait la base de données avant l'ajout de données.