2008-11-26 9 views
58

Quiero escribir pruebas de unidades con NUnit que lleguen a la base de datos. Me gustaría tener la base de datos en un estado constante para cada prueba. Pensé transacciones me permitirían "deshacer" cada prueba así que busqué alrededor y encontró varios artículos 2004-05 sobre el tema:¿Cómo pruebo el código relacionado con la base de datos con NUnit?

Parecen resolver la implementación de un atributo personalizado para NUnit que desarrolla la capacidad de deshacer operaciones de base de datos después r cada prueba se ejecuta.

Eso es grande, pero ...

  1. ¿El existe esta funcionalidad en algún lugar de NUnit forma nativa?
  2. ¿Se ha mejorado esta técnica en los últimos 4 años?
  3. ¿Sigue siendo esta la mejor manera de probar el código relacionado con la base de datos?

Editar: no es que quiera poner a prueba mi DAL específicamente, es más que quiero probar piezas de mi código que interactúan con la base de datos. Para que estas pruebas sean "sin contacto" y repetibles, sería increíble si pudiera restablecer la base de datos después de cada una.

Además, quiero facilitar esto en un proyecto existente que no tiene lugar de prueba en este momento. Por esa razón, prácticamente no puedo crear una base de datos y datos desde cero para cada prueba.

Respuesta

67

NUnit tiene ahora un atributo [Retroceso], pero yo prefiero hacerlo de una manera diferente. Yo uso la clase TransactionScope. Hay un par de formas de usarlo.

[Test] 
public void YourTest() 
{ 
    using (TransactionScope scope = new TransactionScope()) 
    { 
     // your test code here 
    } 
} 

Dado que no le indicó a TransactionScope que confirme, se revertirá automáticamente. Funciona incluso si una afirmación falla o se produce alguna otra excepción.

La otra forma es usar el [SetUp] para crear el TransactionScope y [TearDown] para llamar a Dispose en él. Corta parte de la duplicación de código, pero logra lo mismo.

[TestFixture] 
public class YourFixture 
{ 
    private TransactionScope scope; 

    [SetUp] 
    public void SetUp() 
    { 
     scope = new TransactionScope(); 
    } 

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


    [Test] 
    public void YourTest() 
    { 
     // your test code here 
    } 
} 

Esto es tan seguro como la instrucción using en una prueba individual, porque NUnit garantizará que TearDown se llama.

Habiendo dicho todo eso, creo que las pruebas que llegan a la base de datos no son realmente pruebas unitarias. Todavía los escribo, pero los considero como pruebas de integración. Todavía los veo como proveyendo valor. Un lugar donde los uso a menudo es probar el código LINQ to SQL. No uso el diseñador. Escribo a mano los DTO y los atributos. Me han sabido equivocarme. Las pruebas de integración ayudan a detectar mi error.

+2

Después de usar este enfoque durante un par de semanas, estoy muy contento con él, ¡gracias de nuevo! –

+1

Terminé usando un patrón muy similar, pero con una clase base que se ocupa de los trivios de la base de datos, incluida la configuración de las conexiones y otras cosas. –

+1

El único problema es seguramente si no se compromete, no puede probar los datos que se han confirmado en la base de datos. es decir, me gustaría que mi prueba llame al código que llama a la base de datos, luego haga algunas afirmaciones en la base de datos para verificar los datos, pero finalmente restituya todos esos cambios cuando se complete la prueba o conjunto de pruebas. Aunque es un punto válido decir que estas no son realmente pruebas unitarias. Personalmente, me burlo de DAL en general, pero es útil contar con pruebas explícitas de DB que no se ejecutan en una ejecución automatizada. – tjmoore

1

Llamaré a estas pruebas de integración, pero no importa. Lo que he hecho para tales pruebas es hacer que mis métodos de configuración en la clase de prueba borren todas las tablas de interés antes de cada prueba. Generalmente escribo el SQL a mano para hacer esto, de modo que no estoy usando las clases bajo prueba.

En general, confío en un ORM para mi capa de datos y, por lo tanto, no escribo pruebas de unidad para mucho allí. No siento la necesidad de un código de prueba unitaria que no escriba. Para el código que agrego en la capa, generalmente utilizo la inyección de dependencia para abstraer la conexión real a la base de datos, de modo que cuando pruebo mi código, no toque la base de datos real. Haga esto junto con un marco burlón para obtener mejores resultados.

+0

Desafortunadamente este enfoque no es práctico para mis proyectos (cientos de tablas, procedimientos, gigs de datos). Esto es demasiado fuerte para justificar en un proyecto existente. –

+0

Pero sus pruebas unitarias deben dividirse en clases más pequeñas y enfocadas que no toquen todas las tablas. Solo necesita lidiar con las tablas que esta clase de prueba en particular toca. – tvanfosson

+0

Además, la actualización de pruebas unitarias en proyectos existentes probablemente se realice mejor "según sea necesario", como cuando necesita refactorizar o reparar un error. Luego puede escribir una "caja" de pruebas alrededor del código existente para garantizar que sus cambios no rompan cosas (o corrija el error). – tvanfosson

0

Considere crear un script de base de datos para que pueda ejecutarlo automáticamente desde NUnit, así como manualmente para otros tipos de pruebas. Por ejemplo, si usa Oracle, inicie SqlPlus desde NUnit y ejecute los scripts. Estos scripts generalmente son más rápidos de escribir y más fáciles de leer. Además, muy importante, ejecutar SQL desde Toad o equivalente es más esclarecedor que ejecutar SQL desde el código o pasar por un ORM desde el código. En general, crearé una secuencia de comandos de instalación y desmontaje y los pondré en métodos de configuración y desmontaje.

Si usted debe pasar por la base de datos de pruebas unitarias es otra discusión. Creo que a menudo tiene sentido hacerlo. Para muchas aplicaciones, la base de datos es el centro absoluto de acción, la lógica se basa en gran medida y todas las demás tecnologías y lenguajes y técnicas pasan fantasmas. Y con el auge de los lenguajes funcionales, comenzamos a darnos cuenta de que SQL, como JavaScript, es en realidad un gran lenguaje que estuvo justo debajo de nuestras narices durante todos estos años.

Como un aparte, Linq to SQL (que me gusta en concepto nunca ha usado) casi me parece una forma de hacer SQL sin procesar desde el código sin admitir lo que estamos haciendo. Algunas personas les gusta SQL y saben que les gusta, otros les gusta y no saben que les gusta. :)

Cuestiones relacionadas