Hay respuestas a la pregunta sobre cómo las clases de prueba que se conectan a una base de datos, por ejemplo, "¿Deberían conectarse las clases de prueba de servicio ..." y "Prueba de unidad - Aplicación acoplada a la base de datos" .
Entonces, en resumen, supongamos que tiene una clase A que necesita conectarse a una base de datos. En lugar de dejar que A se conecte realmente, proporciona a A una interfaz que A puede usar para conectarse. Para probar, implementa esta interfaz con algunas cosas, sin conectarse, por supuesto. Si la clase B crea una instancia de A, debe pasar una conexión de base de datos "real" a A. Pero eso significa que B abre una conexión de base de datos. Eso significa que para probar B, se inyecta la conexión en B. Pero B se instancia en la clase C y así sucesivamente.
Entonces, ¿en qué punto debo decir "aquí obtengo datos de una base de datos y no escribiré una prueba unitaria para este fragmento de código"?
En otras palabras: en algún lugar del código en alguna clase debo llamar sqlDB.connect()
o algo similar. ¿Cómo pruebo esta clase?
¿Y es lo mismo con el código que tiene que lidiar con una GUI o un sistema de archivos?
Quiero hacer Unit-Test. Cualquier otro tipo de prueba no está relacionado con mi pregunta. Sé que solo probaré una clase con ella (estoy muy de acuerdo contigo, Kilian). Ahora, alguna clase tiene que conectarse a un DB. Si quiero probar esta clase y preguntar "¿Cómo hago esto?", Muchos dicen: "¡Use la inyección de dependencia!" Pero eso solo cambia el problema a otra clase, ¿no? Entonces pregunto, ¿cómo pruebo la clase que realmente establece la conexión?
Pregunta extra: Algunas respuestas aquí se reducen a "¡Usa objetos simulados!" Qué significa eso? Me burlo de las clases de las que depende la clase bajo prueba. ¿Debo burlarme de la clase bajo prueba ahora y realmente probar la simulación (que se acerca a la idea de usar Métodos de plantilla, ver más abajo)?
fuente
Respuestas:
El objetivo de una prueba unitaria es probar una clase (de hecho, generalmente debería probar un método ).
Esto significa que cuando se prueba la clase
A
, se inyecta una base de datos de prueba en él - algo auto-escrito, o una velocidad del rayo base de datos en la memoria, lo que hace el trabajo.Sin embargo, si prueba la clase
B
, de la cual es clienteA
, generalmente se burla delA
objeto completo con otra cosa, presumiblemente algo que hace su trabajo de una manera primitiva y preprogramada, sin usar unA
objeto real y ciertamente sin usar datos. base (a menos queA
pase toda la conexión de la base de datos a su interlocutor, pero eso es tan horrible que no quiero pensar en ello). Del mismo modo, cuando escribe una prueba unitaria para la claseC
, de la cual es clienteB
, se burlaría de algo que toma el papelB
y se olvidaría porA
completo.Si no lo hace, ya no es una prueba unitaria, sino una prueba de sistema o integración. Esos también son muy importantes, pero una caldera de peces completamente diferente. Para empezar, suelen ser más difíciles de configurar y ejecutar, no es factible exigir pasarlos como condición previa para los registros, etc.
fuente
Realizar pruebas unitarias contra una conexión de base de datos es perfectamente normal y una práctica común. Simplemente no es posible crear un
purist
enfoque donde todo en su sistema sea inyectable por dependencia.La clave aquí es probar contra una base de datos temporal o de prueba solamente, y tener el proceso de inicio más ligero posible para construir esa base de datos de prueba.
Para pruebas unitarias en CakePHP hay cosas llamadas
fixtures
. Los accesorios son tablas de bases de datos temporales creadas sobre la marcha para una prueba unitaria. El dispositivo tiene métodos convenientes para crearlos. Pueden recrear un esquema de una base de datos de producción dentro de la base de datos de prueba, o puede definir el esquema usando una notación simple.La clave del éxito con esto es no implementar la base de datos de negocios, sino enfocarse solo en el aspecto del código que está probando. Si tiene una prueba unitaria que verifica que un modelo de datos solo lee documentos publicados, entonces el esquema de la tabla para esa prueba solo debe tener los campos requeridos por ese código. No tiene que volver a implementar una base de datos de administración de contenido completa solo para probar ese código.
Algunas referencias adicionales.
http://en.wikipedia.org/wiki/Test_fixture
http://phpunit.de/manual/3.7/en/database.html
http://book.cakephp.org/2.0/en/development/testing.html#fixtures
fuente
Hay, en algún lugar de su base de código, una línea de código que realiza la acción real de conectarse a la base de datos remota. Esta línea de código es, 9 veces en 10, una llamada a un método "incorporado" proporcionado por las bibliotecas de tiempo de ejecución específicas para su idioma y entorno. Como tal, no es "su" código y, por lo tanto, no necesita probarlo; a los fines de una prueba unitaria, puede confiar en que esta llamada al método funcionará correctamente. Lo que puede y debe probar en su conjunto de pruebas unitarias son cosas como asegurarse de que los parámetros que se utilizarán para esta llamada sean los que espera que sean, como asegurarse de que la cadena de conexión sea correcta, o la instrucción SQL o nombre del procedimiento almacenado
Este es uno de los propósitos detrás de la restricción de que las pruebas unitarias no deben abandonar su "caja de arena" en tiempo de ejecución y depender del estado externo. En realidad es bastante práctico; El propósito de una prueba unitaria es verificar que el código que escribió (o está a punto de escribir, en TDD) se comporta de la manera que pensó que lo haría. El código que no escribió, como la biblioteca que está utilizando para realizar las operaciones de su base de datos, no debe ser parte del alcance de ninguna prueba unitaria, por la sencilla razón de que no lo escribió.
En su conjunto de pruebas de integración , estas restricciones son relajadas. Ahora puedesdiseñe pruebas que toquen la base de datos, para asegurarse de que el código que escribió se reproduzca bien con el código que no escribió. Sin embargo, estos dos conjuntos de pruebas deben permanecer separados porque su conjunto de pruebas unitarias es más efectivo cuanto más rápido se ejecute (por lo que puede verificar rápidamente que todas las afirmaciones hechas por los desarrolladores sobre su código aún se mantienen), y casi por definición, una prueba de integración es más lento en órdenes de magnitud debido a las dependencias agregadas en recursos externos. Deje que el robot de compilación se encargue de ejecutar su suite de integración completa cada pocas horas, ejecutando las pruebas que bloquean los recursos externos, para que los desarrolladores no se pisen los pies al ejecutar estas mismas pruebas localmente. Y si la construcción se rompe, ¿y qué? Se le da mucha más importancia a garantizar que el build-bot nunca falle una compilación de lo que probablemente debería ser.
Ahora, cuán estrictamente puede cumplir esto depende de su estrategia exacta para conectarse y consultar la base de datos. En muchos casos en los que debe usar el marco de acceso a datos "básico", como los objetos SqlConnection y SqlStatement de ADO.NET, un método completo desarrollado por usted puede consistir en llamadas a métodos integrados y otro código que depende de tener un conexión de base de datos, por lo que lo mejor que puede hacer en esta situación es burlarse de toda la función y confiar en sus conjuntos de pruebas de integración. También depende de qué tan dispuesto esté a diseñar sus clases para permitir que se reemplacen líneas de código específicas para fines de prueba (como la sugerencia de Tobi del patrón de Método de plantilla, que es bueno porque permite "simulacros parciales"
Si su modelo de persistencia de datos se basa en el código de su capa de datos (como desencadenantes, procesos almacenados, etc.), simplemente no hay otra forma de ejercer el código que usted mismo está escribiendo que desarrollar pruebas que vivan dentro de la capa de datos o crucen límite entre el tiempo de ejecución de su aplicación y el DBMS. Un purista diría que este patrón, por esta razón, debe evitarse en favor de algo como un ORM. No creo que vaya tan lejos; Incluso en la era de las consultas integradas en el lenguaje y otras operaciones de persistencia dependientes del dominio verificadas por el compilador, veo el valor de bloquear la base de datos solo para las operaciones expuestas a través del procedimiento almacenado, y por supuesto, dichos procedimientos almacenados deben verificarse utilizando pruebas Pero, tales pruebas no son pruebas unitarias . Son integracion pruebas
Si tiene un problema con esta distinción, generalmente se basa en una gran importancia otorgada a la "cobertura de código" completa, también conocida como "cobertura de prueba unitaria". Desea asegurarse de que cada línea de su código esté cubierta por una prueba unitaria. Un noble objetivo en su cara, pero digo tonterías; esa mentalidad se presta a antipatrones que se extienden mucho más allá de este caso específico, como escribir pruebas sin afirmaciones que se ejecutan pero no ejercentu codigo. Estos tipos de ejecuciones finales únicamente por el bien de los números de cobertura son más dañinos que relajar su cobertura mínima. Si desea asegurarse de que cada línea de su base de código se ejecute mediante alguna prueba automatizada, entonces eso es fácil; cuando calcule métricas de cobertura de código, incluya las pruebas de integración. Incluso podría ir un paso más allá y aislar estas disputadas pruebas "Itino" ("Integración solo de nombre"), y entre su conjunto de pruebas unitarias y esta subcategoría de pruebas de integración (que aún debería ejecutarse razonablemente rápido) debería obtener maldición casi cerca de la cobertura total.
fuente
Las pruebas unitarias nunca deben conectarse a una base de datos. Por definición, deben probar una sola unidad de código cada uno (un método) en total aislamiento del resto de su sistema. Si no lo hacen, entonces no son una prueba unitaria.
Dejando a un lado la semántica, hay una gran cantidad de razones por las cuales esto es beneficioso:
Las pruebas unitarias son una forma de verificar su trabajo. Deben delinear todos los escenarios para un método dado, lo que generalmente significa todas las diferentes rutas a través de un método. Es su especificación que está construyendo, similar a la contabilidad de doble entrada.
Lo que está describiendo es otro tipo de prueba automatizada: una prueba de integración. Si bien también son muy importantes, idealmente tendrá mucho menos de ellos. Deben verificar que un grupo de unidades se integren entre sí correctamente.
Entonces, ¿cómo se prueban las cosas con el acceso a la base de datos? Todo su código de acceso a datos debe estar en una capa específica, por lo que el código de su aplicación puede interactuar con servicios simulables en lugar de la base de datos real. No debería importarle si esos servicios están respaldados por algún tipo de base de datos SQL, datos de prueba en memoria o incluso datos de servicios web remotos. Esa no es su preocupación.
Idealmente (y esto es muy subjetivo), desea que la mayor parte de su código esté cubierto por pruebas unitarias. Esto le da la confianza de que cada pieza funciona de forma independiente. Una vez que las piezas están construidas, debes unirlas. Ejemplo: cuando escribo la contraseña del usuario, debería obtener esta salida exacta.
Digamos que cada componente se compone de aproximadamente 5 clases: querría probar todos los puntos de falla dentro de ellos. Esto equivale a muchas menos pruebas solo para garantizar que todo esté conectado correctamente. Ejemplo: pruebe si puede encontrar al usuario de la base de datos con un nombre de usuario / contraseña.
Finalmente, desea algunas pruebas de aceptación para asegurarse de cumplir con los objetivos comerciales. Hay incluso menos de estos; pueden asegurarse de que la aplicación se esté ejecutando y haga lo que fue diseñada para hacer. Ejemplo: dados estos datos de prueba, debería poder iniciar sesión.
Piense en estos tres tipos de pruebas como una pirámide. Necesitas muchas pruebas unitarias para soportar todo, y luego avanzas desde allí.
fuente
El patrón de método de plantilla podría ayudar.
Envuelve las llamadas a una base de datos en
protected
métodos. Para probar esta clase, realmente prueba un objeto falso que hereda de la clase de conexión de base de datos real y anula los métodos protegidos.De esta forma, las llamadas reales a la base de datos nunca se someten a pruebas unitarias, es cierto. Pero son solo estas pocas líneas de código. Y eso es aceptable.
fuente
Las pruebas con datos externos son pruebas de integración. Prueba de unidad significa que solo está probando la unidad. Se realiza principalmente con su lógica de negocios. Para que su unidad de código sea comprobable, debe seguir algunas pautas, como hacer que su unidad sea independiente de otra parte de su código. Durante la prueba unitaria, si necesita datos, debe inyectar esos datos con fuerza mediante inyección de dependencia. Hay un marco de burlas y tropezones por ahí.
fuente