11

? Debido a las posibles diferencias entre Linq-to-Entities (EF4) y Linq-to-Objects, necesito usar una base de datos real para asegurar que mis clases de consulta recuperen datos de EF correctamente. Sql CE 4 parece ser la herramienta perfecta para esto, sin embargo he tenido algunos contratiempos. Estas pruebas están usando MsTest.¿Cómo puedo usar bases de datos Sql CE 4 para pruebas funcionales

El problema que tengo es si la base de datos no se vuelve a crear (debido a cambios en el modelo), los datos se siguen añadiendo a la base de datos después de cada prueba sin eliminar nada de los datos. Esto puede provocar conflictos en las pruebas, ya que las consultas devuelven más datos de los previstos.

Mi primera idea fue inicializar un TransactionScope en el método TestInitialize y deshacerme de la transacción en TestCleanup. Desafortunadamente, Sql CE4 no admite transacciones.

Mi siguiente idea fue eliminar la base de datos en TestCleanup a través de una llamada File.Delete(). Desafortunadamente, esto parece no funcionar después de ejecutar la primera prueba, ya que la primera prueba es TestCleanup, parece eliminar la base de datos, pero cada prueba después de la primera no parece recrear la base de datos, y por lo tanto da un error que la base de datos archivo no se encuentra

he tratado de cambiar TestInitialize y TestCleanup etiquetas a ClassInitialize y ClassCleanup para mi clase de pruebas, pero que con errores con un NullReferenceException debido a la prueba de funcionamiento antes de la ClassInitialize (o al menos eso parece. ClassInitialize es en la clase base y quizás por causando eso).

Se me acabaron las formas de usar efectivamente Sql CE4 para probar. Alguien tiene mejores ideas?


Editar: Terminé encontrando una solución. En mi clase base de prueba de unidad de EF, inicio una nueva instancia de mi contexto de datos y luego llamo al context.Database.Delete() y context.Database.Create(). Las pruebas unitarias corren un poco más lento, pero ahora se prueba unitaria efectiva usando una base de datos real


edición final: Después de algunos correos electrónicos de ida y vuelta con Microsoft, resulta que TransactionScope s están ahora autorizados en SQLCE con la última versión de SqlCE. Sin embargo, si está utilizando EF4, existen algunas limitaciones en cuanto a que debe abrir explícitamente la conexión de la base de datos antes de comenzar la transacción. El siguiente código muestra un ejemplo de cómo usar correctamente SQL CE para la unidad/pruebas funcionales:

[TestMethod] 
    public void My_SqlCeScenario() 
    { 
     using (var context = new MySQLCeModelContext()) //ß derived from DbContext 
     { 
      ObjectContext objctx = ((IObjectContextAdapter)context).ObjectContext; 
      objctx.Connection.Open(); //ß Open your connection explicitly 
      using (TransactionScope tx = new TransactionScope()) 
      { 

       var product = new Product() { Name = "Vegemite" }; 
       context.Products.Add(product); 
       context.SaveChanges(); 
      } 
      objctx.Connection.Close(); //ß close it when done! 
     } 
    } 
+0

Por supuesto, SQL CE admite transacciones ... pero usar TransactionScope es una forma muy incorrecta de hacerlo. Solo hazlo normalmente a través del objeto Connection. – leppie

+0

No estoy seguro de cómo con entidades EF4 sin 'TransactionScope', a menos que quiera decir que no llama a' SaveChanges() ', lo que significa que las pruebas no son válidas. – KallDrexx

+0

¿Puede proporcionar un ejemplo de cómo sembrar los datos con Sql CE? Utilizo EF6 y me gustaría probarlo usando sql ce –

Respuesta

4

En su TestInitialize usted debe hacer lo siguiente:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new System.Data.Entity.Database.DropCreateDatabaseAlways<YourEntityFrameworkClass>()); 

Esto provocará marco de la entidad para recrear siempre la base de datos cada vez que se ejecuta la prueba.

Por cierto, puede crear una clase alternativa que hereda de DropCreateDatabaseAlways. Esto le permitirá sembrar su base de datos con datos establecidos cada vez.

public class DataContextInitializer : DropCreateDatabaseAlways<YourEntityFrameworkClass> { 
    protected override void Seed(DataContext context) { 
     context.Users.Add(new User() { Name = "Test User 1", Email = "[email protected]" }); 
     context.SaveChanges(); 
    } 
} 

Luego, en su inicialización que iba a cambiar la llamada a:

System.Data.Entity.Database.DbDatabase.SetInitializer<YourEntityFrameworkClass>(
    new DataContextInitializer()); 
+0

Después de tener finalmente la oportunidad de verificar esto, esto no parece funcionar. Parece que se está abandonando la base de datos antes de ejecutar cualquier prueba, pero no se descarta la base de datos antes de ejecutar cada prueba individualmente. Esto está causando que los datos persistan en las pruebas unitarias, lo que ocasiona que algunas otras pruebas fallen. Puedo solucionarlo por el momento, pero preferiría que funcione sin eso. – KallDrexx

+0

No importa, es demasiado intrincado y no presta bien a las pruebas unitarias ... Básicamente lo uso para mapear múltiples tablas en una sola entidad y se hace en un nuevo ensamblado cada vez que lo llamo para esa tabla específica ... difícil de explicar realmente :) – Buildstarted

+0

¿No podría simplemente limpiar las tablas después de cada prueba y volver a poblarlas con el método de semilla? ¿O cada prueba es un conjunto diferente de tablas y qué no? – Buildstarted

3

me encontré con el enfoque de la "edición final" funciona para mí también. Sin embargo, es REALMENTE molesto. No es solo para pruebas, sino en cualquier momento que desee usar TransactionScope con Entity Framework y SQL CE. Quiero codificar una vez y hacer que mi aplicación sea compatible con SQL Server y SQL CE, pero en cualquier lugar que use transacciones, tengo que hacer esto. ¡Seguramente el equipo de Entity Framework debería haber manejado esto por nosotros!

Mientras tanto, llegué un paso más allá para que sea un poco más limpio en mi código. Añadir este bloque a su contexto de datos (sea cual sea la clase que se deriva de DbContext):

public MyDataContext() 
{ 
    this.Connection.Open(); 
} 

protected override void Dispose(bool disposing) 
{ 
    if (this.Connection.State == ConnectionState.Open) 
     this.Connection.Close(); 

    base.Dispose(disposing); 
} 

private DbConnection Connection 
{ 
    get 
    { 
     var objectContextAdapter = (IObjectContextAdapter) this; 
     return objectContextAdapter.ObjectContext.Connection; 
    } 
} 

Esto hace que sea mucho más limpio cuando realmente se utilicen:

using (var db = new MyDataContext()) 
{ 
    using (var ts = new TransactionScope()) 
    { 
     // whatever you need to do 

     db.SaveChanges(); 
     ts.Complete(); 
    } 
} 

Aunque supongo que si el diseño de su aplicación tal que todos los cambios se confirman en una sola llamada a SaveChanges(), entonces la transacción implícita sería lo suficientemente buena. Para el escenario de prueba, queremos revertir todo en lugar de llamar a ts.Complete(), por lo que ciertamente se requiere allí. Estoy seguro de que hay otros escenarios en los que necesitamos el alcance de transacción disponible. Es una pena que no sea compatible directamente con EF/SQLCE.

+0

Solución interesante. Acabo de hacer una solución donde mi clase 'IUnitOfWork' tiene los métodos' BeginTransaction() 'y' EndTransaction (bool commit) '. 'BeginTransaction' abrirá la conexión si todavía no existe, y' EndTransaction' me permitirá completar o deshacer la transacción fácilmente. Hasta ahora, esto parece funcionar también. Afortunadamente en el futuro, aunque podría usar los ámbitos de transacción por sí mismos. – KallDrexx

Cuestiones relacionadas