¿Cómo pruebo el código relacionado con la base de datos con NUnit?

Quiero escribir pruebas unitarias 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é que las transacciones me permitirían “deshacer” cada prueba, así que busqué y encontré varios artículos de 2004-05 sobre el tema:

  • http://weblogs.asp.net/rosherove/archive/2004/07/12/180189.aspx
  • http://weblogs.asp.net/rosherove/archive/2004/10/05/238201.aspx
  • http://davidhayden.com/blog/dave/archive/2004/07/12/365.aspx
  • http://haacked.com/archive/2005/12/28/11377.aspx

Estos parecen resolverse en torno a la implementación de un atributo personalizado para NUnit que se basa en la capacidad de deshacer operaciones DB después de cada prueba se ejecuta.

Eso es genial, pero …

  1. ¿Esta funcionalidad existe en algún lugar de NUnit de 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 probar mi DAL específicamente, es más que quiero probar partes 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.

NUnit ahora tiene un atributo [Rollback], pero prefiero hacerlo de otra manera. 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 } } 

Como 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 el uso de la statement en una prueba individual porque NUnit garantizará que se llame a TearDown.

Habiendo dicho todo eso, creo que las pruebas que llegan a la base de datos no son realmente pruebas unitarias. Aún 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.

Acabo de ir a un grupo de usuarios de .NET y el presentador dijo que usó SQLlite en la configuración de prueba y desassembly, y que utilizó la opción en memoria. Tenía que cambiar un poco la conexión y explícitamente destruir la conexión, pero daría una base de datos limpia todo el tiempo.

http://houseofbilz.com/archive/2008/11/14/update-for-the-activerecord-quotmockquot-framework.aspx

Para este tipo de pruebas, experimenté con NDbUnit (trabajando en concierto con NUnit). Si la memoria sirve, era un puerto de DbUnit de la plataforma Java. Tenía muchos comandos ingeniosos para el tipo de cosas que intentas hacer. El proyecto parece haberse mudado aquí:

http://code.google.com/p/ndbunit/

(solía estar en http://ndbunit.org ).

La fuente parece estar disponible a través de este enlace: http://ndbunit.googlecode.com/svn/trunk/

Llamaría 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 unitarias 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 dependency injection 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 mocking para obtener mejores resultados.

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 desassembly y los pondré en métodos de configuración y desassembly.

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.

Solo como un aparte, Linq to SQL (que me gusta en concepto, aunque nunca he 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. 🙂