2012-03-25 15 views
15

Considere el siguiente ejemplo de una prueba unitaria. Los comentarios explican bastante mi problema.¿Siempre limpiar después de una prueba?

[TestMethod] 
public void MyTestMethod() 
{ 

    //generate some objects in the database 
    ... 

    //make an assert that fails sometimes (for example purposes, this fails always) 
    Assert.IsTrue(false); 

    //TODO: how do we clean up the data generated in the database now that the test has ended here? 

} 
+0

También debe usar 'TearDown' (o su equivalente en su suite) para limpiar, ya que si la prueba falla, el código de limpieza no se ejecutará. –

+0

¿Qué le parece tomar una excepción y luego volver a tirarla después de limpiarla? –

+0

Jimmy Bogard (autor de * AutoMapper *) tiene un excelente artículo y herramienta: [Pruebas de bases de datos confiables con Respawn] (https://lostechies.com/jimmybogard/2015/02/19/reliable-database-tests-with-respawn /) –

Respuesta

24

Hay dos formas de hacerlo. One está utilizando los atributos TestInitialize y TestCleanup en los métodos de la clase de prueba. Siempre se ejecutarán antes y después de la prueba, respectivamente.

Otra forma es utilizar el hecho de que las fallas de prueba se propagan al corredor de prueba a través de excepciones. Esto significa que un bloque try {} finally {} en su prueba se puede utilizar para limpiar cualquier cosa después de que falle una afirmación.

[TestMethod] 
public void FooTest() 
{ 
    try 
    { 
    // setup some database objects 
    Foo foo = new Foo(); 
    Bar bar = new Bar(foo); 
    Assert.Fail(); 
    } 
    finally 
    { 
    // remove database objects. 
    } 
} 

La limpieza try/finally puede ser realmente complicada si hay muchos objetos para limpiar. Mi equipo se ha inclinado hacia una clase de ayuda que implementa IDisposable. Realiza un seguimiento de los objetos que se han creado y los empuja a una pila. Cuando se llama a Dispose, los elementos se extraen de la pila y se eliminan de la base de datos.

[TestMethod] 
public void FooTest() 
{ 
    using (FooBarDatabaseContext context = new FooBarDatabaseContext()) 
    { 
    // setup some db objects. 
    Foo foo = context.NewFoo(); 
    Bar bar = context.NewBar(foo); 
    Assert.Fail(); 
    } // calls dispose. deletes bar, then foo. 
} 

Esto tiene la ventaja adicional de ajustar los constructores en las llamadas a métodos. Si las firmas de los constructores cambian, podemos modificar fácilmente el código de prueba.

+9

Me encanta criticar; TestCleanup no se ejecuta si TestInitialize lanzó una excepción. –

+4

@ carlin.scott Su nitpicking acaba de salvar mi día –

6

Un par de respuestas:

  1. Si se trata de utilizar una base de datos real, yo diría que no es una "prueba de unidad" en el sentido más estricto del término. Es una prueba de integración. Una prueba unitaria no debería tener tales efectos secundarios. Considere usar una biblioteca burlona para simular la base de datos real. Rhino Mocks es uno, pero hay muchos otros.

  2. Si, sin embargo, todo el punto de esta prueba es para interactuar efectivamente con una base de datos, entonces usted desea interactuar con una base de datos de prueba de sólo transitoria. En ese caso, parte de las pruebas automatizadas incluiría código para construir la base de datos de prueba desde cero, luego ejecutar las pruebas y luego destruir la base de datos de prueba. Una vez más, la idea es no tener efectos secundarios externos. Probablemente existan varias formas de hacerlo, y no estoy lo suficientemente familiarizado con los marcos de pruebas unitarias para realmente dar una sugerencia concreta. Pero si está utilizando las pruebas integradas en Visual Studio, entonces tal vez sería útil usar Visual Studio Database Project.

1

Tu pregunta es demasiado general. Por lo general, debes limpiar después de cada prueba individual. Por lo general, no puede confiar en que todas las pruebas se ejecuten siempre en el mismo orden y debe estar seguro de lo que hay en su base de datos. Para la configuración general o la limpieza, la mayoría de los marcos de prueba de la unidad proporcionan métodos setUp y tearDown que puede anular y que serán llamados automáticamente. No sé cómo funciona eso en C# pero e. gramo. en JUnit (Java) tienes estos métodos.

Estoy de acuerdo con David. Sus exámenes generalmente no deberían tener efectos secundarios. Debe configurar una nueva base de datos para cada prueba individual.

0

Tendrás que hacer una limpieza manual en esta circunstancia. Es decir, lo contrario de generar algunos objetos en el db.

La alternativa, es utilizar herramientas que imita como burla de Rhino para que la base de datos es sólo una base de datos en memoria

0

Depende de lo que son en realidad pruebas. Mirando los comentarios, diría , pero por cierto es difícil de deducir mirando los comentarios. Limpiar el objeto que acaba de insertarlo, en la práctica, restablecer el estado de la prueba. Entonces, si realiza la limpieza, comienza la prueba del sistema de limpieza.

10

Creo que la mejor respuesta en situaciones como esta es pensar con mucho cuidado sobre lo que está tratando de probar. Idealmente, una prueba unitaria debería intentar probar un solo hecho sobre un único método o función. Cuando comienzas a combinar muchas cosas, se cruza con el mundo de las pruebas de integración (que son igualmente valiosas, pero diferentes).

Para probar la unidad, para que pueda probar solo lo que desea probar, necesitará diseño para probar. Esto generalmente implica el uso adicional de interfaces (estoy asumiendo .NET a partir del código que mostró) y alguna forma de inyección de dependencia (pero no requiere un contenedor IoC/DI a menos que desee uno). También se beneficia de, y lo alienta a crear clases muy cohesivas (de propósito único) y desacopladas (dependencias de software) en su sistema.

Por lo tanto, cuando prueba una lógica de negocios que depende de los datos de una base de datos, normalmente usaría algo como el Repository Pattern e inyectaría un fake/stub/mock IXXXRepository para pruebas unitarias. Cuando está probando el repositorio concreto, o bien necesita hacer el tipo de limpieza de la base de datos que está solicitando o necesita ajustar/anular la llamada a la base de datos subyacente. Eso depende de ti.

Cuando necesite crear/rellenar/limpiar la base de datos, puede considerar aprovechar los diversos métodos de instalación y desmontaje disponibles en la mayoría de los marcos de prueba. Pero tenga cuidado, porque algunos de ellos se ejecutan antes y después de cada prueba, lo que puede afectar seriamente el rendimiento de las pruebas de su unidad. Las pruebas que se ejecutan demasiado despacio no se ejecutarán con mucha frecuencia, y eso es malo.

En MS-Test, los atributos que usaría para declarar instalación/desmontaje son ClassInitialize, ClassCleanUp, TestInitialize, TestCleanUp. Otros marcos tienen construcciones con nombres similares.

Hay una serie de marcos que pueden ayudarle con la burla/stubbing: Moq, Rhino Mocks, NMock, TypeMock, Moles and Stubs (VS2010), VS11 Fakes (VS11 Beta), etc. Si está buscando marcos de inyección de dependencia, mira cosas como Ninject, Unity, Castle Windsor, etc.

0

Creo que la limpieza depende de cómo esté compilando los datos, por lo que si "los datos de las pruebas anteriores" no interactúan con las pruebas futuras, creo que está bien dejarlo atrás.

Un enfoque que he estado tomando al escribir pruebas de integración es hacer que las pruebas se ejecuten contra un archivo db diferente al de la aplicación db. Tiendo a reconstruir el db de prueba como una precondición para cada ejecución de prueba. De esta forma, no necesita un esquema de limpieza granular para cada prueba ya que cada ejecución de prueba se borra entre las ejecuciones. La mayor parte de mi desarrollo lo he hecho utilizando SQL server, pero en algunos casos he ejecutado mis pruebas en una versión SQL Compact db, que es rápido y eficiente para reconstruir entre ejecuciones.

0

mbUnit tiene un atributo muy útil Rollback que limpia la base de datos después de terminar la prueba. Sin embargo, deberá configurar el DTC (Coordinador de transacciones distribuidas) para poder usarlo.

0

Estaba teniendo un problema similar en el que la afirmación de una prueba impedía la limpieza y hacía que fallaran otras pruebas.

Esperemos que esto sea de utilidad para alguien, alguna vez.

[Test] 
    public void Collates_Blah_As_Blah() 
    { 
     Assert.False(SINGLETON.Collection.Any()); 

     for (int i = 0; i < 2; i++) 
      Assert.That(PROCESS(ValidRequest) == Status.Success); 

     try 
     { 
      Assert.AreEqual(1, SINGLETON.Collection.Count); 
     } 
     finally 
     { 
      SINGLETON.Collection.Clear(); 
     } 
    } 

El bloque finally se ejecutará si la afirmación de pasa o no, sino que también no introduce el riesgo de falsos pases - lo que hará que la captura!

Cuestiones relacionadas