2008-08-22 16 views
32

Este verano pasado estaba desarrollando una aplicación CRUD de servidor ASP.NET/SQL básica, y la prueba de unidades era uno de los requisitos. Me encontré con algunos problemas cuando intenté contra la base de datos. A mi entender, las pruebas unitarias deben ser:Bases de datos de pruebas unitarias

  • sin estado
  • independientes entre sí
  • repetible con el mismo resultado, es decir, sin que persiste cambia

Estos requisitos parecen estar en desacuerdo con uno otro cuando se desarrolla para una base de datos. Por ejemplo, no puedo probar Insertar() sin asegurarme de que las filas que se van a insertar aún no están allí, por lo tanto, necesito llamar a Eliminar() primero. Pero, ¿y si no están ya allí? Entonces necesitaría llamar primero a la función Exists().

Mi solución final implicó funciones de configuración muy grandes (¡asqueroso!) Y un caso de prueba vacío que se ejecutaría primero e indicaría que la configuración se ejecutó sin problemas. Esto se está sacrificando por la independencia de las pruebas mientras se mantiene su apatridia.

Otra solución que encontré es envolver las llamadas a función en una transacción que se puede revertir fácilmente, como Roy Osherove's XtUnit. Este trabajo, pero implica otra biblioteca, otra dependencia, y parece una solución demasiado pesada para el problema en cuestión.

Entonces, ¿qué ha hecho la comunidad SO cuando se enfrenta a esta situación?


tgmdbm dijo:

Normalmente, se utiliza el marco de la unidad de pruebas automatizado favorito para realizar pruebas de integración, que es por qué algunas personas se confunden, pero no siguen la misma reglas. Usted es permitido implicar la implementación concreta de muchas de sus clases (porque han sido probadas en unidades). Está probando cómo sus clases concretas interactúan entre sí y con la base de datos.

Así que si leo esto correctamente, no hay realmente ninguna manera de forma eficaz unidad de prueba de una capa de acceso a datos. O, ¿una "prueba unitaria" de una capa de acceso a datos implicará probar, por ejemplo, los comandos SQL/generados por las clases, independientemente de la interacción real con la base de datos?

Respuesta

25

No hay una manera real de probar la unidad de una base de datos que no sea afirmar que las tablas existen, contener las columnas esperadas y tener las restricciones apropiadas. Pero eso generalmente no vale la pena hacerlo.

No suele ser unidad prueba la base de datos. Por lo general, implica la base de datos en las pruebas de integración .

Por lo general, utiliza su marco de prueba de unidades automatizado favorito para realizar pruebas de integración, por lo que algunas personas se confunden, pero no siguen las mismas reglas. Se le permite involucrar la implementación concreta de muchas de sus clases (porque han sido probadas en unidades).Está probando cómo interactúan sus clases concretas entre sí y con la base de datos.

11

DBunit

Puede utilizar esta herramienta para exportar el estado de una base de datos en un momento dado, y luego, cuando esté la unidad de pruebas, que se pueden revertir a su estado anterior automáticamente al comienzo de la pruebas. Lo usamos con bastante frecuencia donde trabajo.

5

La solución habitual para las dependencias externas en pruebas unitarias es utilizar objetos simulados, es decir, bibliotecas que imitan el comportamiento de las reales contra las que está probando. Esto no siempre es sencillo, ya veces requiere un poco de ingenio, pero hay varios buenos (Freeware) bibliotecas simulacros que hay para .Net si no quiere "hágalo usted mismo". Dos vienen a la mente de inmediato:

Rhino Mocks es uno que tiene una muy buena reputación.

NMock es otra.

Hay un montón de bibliotecas simulacros comerciales disponibles, también. Parte de escribir las pruebas unitarias buenos es en realidad desinging su código para ellos - por ejemplo, mediante el uso de las interfaces donde tiene sentido, para que pueda "simulacro" un objeto dependiente de implmenting una versión "falsa" de su interfaz, que sin embargo se comporta de una manera predecible, para propósitos de prueba.

En los simulacros de bases de datos, esto significa "burlarse" de su propia capa de acceso a bases de datos con objetos que devuelven objetos de tablas, filas o conjuntos de datos para las pruebas unitarias.

Donde trabajo, normalmente hacemos nuestras propias librerías simuladas desde cero, pero eso no significa que tenga que hacerlo.

1

Lo que debe hacer es ejecutar sus pruebas desde una copia en blanco de la base de datos que genera desde un script. Puede ejecutar sus pruebas y luego analizar los datos para asegurarse de que tiene exactamente lo que debería después de ejecutar sus pruebas. Luego, simplemente borre la base de datos, ya que es un regalo. Todo esto puede automatizarse y puede considerarse una acción atómica.

4

Sí, debe refactorizar su código para acceder a Repositorios y Servicios que acceden a la base de datos y luego puede simular o resguardar esos objetos para que el objeto bajo prueba nunca toque la base de datos. ¡Esto es mucho más rápido que almacenar el estado de la base de datos y restablecerla después de cada prueba!

Recomiendo encarecidamente Moq como marco de burla. He usado Rhino Mocks y NMock. Moq era tan simple y resolvió todos los problemas que tuve con los otros frameworks.

0

Si está utilizando LINQ to SQL como ORM, puede generar la base de datos sobre la marcha (siempre que tenga suficiente acceso desde la cuenta utilizada para la prueba de la unidad). Ver http://www.aaron-powell.com/blog.aspx?id=1125

2

He tenido la misma pregunta y he llegado a las mismas conclusiones básicas que las otras respuestas aquí: No moleste en probar la capa de comunicación DB real, pero si quiere probar sus funciones de modelo (para asegurarse de que están extrayendo los datos correctamente, formateándolo correctamente, etc.), utilice algún tipo de fuente de datos ficticia y pruebas de configuración para verificar la recuperación de los datos.

También encuentro que la definición escueta de pruebas unitarias es un ajuste pobre para muchas actividades de desarrollo web.Sin embargo, esta página se describen algunos modelos de pruebas unitarias más 'avanzados' y puede ayudar a inspirar a algunas ideas para la aplicación de la unidad de pruebas en diversas situaciones:

Unit Test Patterns

2

me explicó una técnica que he estado usando por esta misma situación here .

La idea básica es ejercitar cada método en su DAL - afirmar sus resultados - y cuando cada prueba se complete, desmantelar para que su base de datos esté limpia (no hay datos basura/prueba).

El único problema que quizás no encuentre "excelente" es que normalmente realizo una prueba CRUD completa (no pura desde la perspectiva de la unidad de evaluación) pero esta prueba de integración le permite ver su código de mapeo CRUD + en acción. De esta forma, si se rompe, sabrá antes de iniciar la aplicación (me ahorra una tonelada de trabajo cuando estoy tratando de ir rápido)

1

La prueba de la capa de datos y la base de datos juntos deja pocas sorpresas para más adelante en el proyecto. Pero las pruebas en la base de datos tienen sus problemas, el principal es que está probando contra el estado compartido por muchas pruebas. Si inserta una línea en la base de datos en una prueba, la siguiente prueba también puede ver esa línea.
Lo que necesita es una forma de deshacer los cambios que realiza en la base de datos.
La clase TransactionScope es lo suficientemente inteligente como para manejar transacciones muy complicadas, , así como transacciones anidadas en las que su código en llamadas de prueba se compromete en su propia transacción local . He aquí un simple trozo de código que muestra lo fácil que es añadir capacidad de reversión a sus pruebas:

[TestFixture] 
    public class TrannsactionScopeTests 
    { 
     private TransactionScope trans = null; 

     [SetUp] 
     public void SetUp() 
     { 
      trans = new TransactionScope(TransactionScopeOption.Required); 
     } 

     [TearDown] 
     public void TearDown() 
     { 
      trans.Dispose(); 
     } 

     [Test] 
     public void TestServicedSameTransaction() 
     { 
      MySimpleClass c = new MySimpleClass(); 
      long id = c.InsertCategoryStandard("whatever"); 
      long id2 = c.InsertCategoryStandard("whatever"); 
      Console.WriteLine("Got id of " + id); 
      Console.WriteLine("Got id of " + id2); 
      Assert.AreNotEqual(id, id2); 
     } 
    }