database - soporta - optimizar consultas lentas mysql
Cómo probar un objeto con consultas de base de datos (13)
He oído que las pruebas unitarias son "totalmente increíbles", "realmente geniales" y "todo tipo de cosas buenas", pero el 70% o más de mis archivos implican acceso a la base de datos (algunos leen y escriben) y no estoy seguro de cómo para escribir una prueba unitaria para estos archivos.
Estoy usando PHP y Python, pero creo que es una pregunta que se aplica a la mayoría de los idiomas que usan el acceso a la base de datos.
Configurar los datos de prueba para las pruebas unitarias puede ser un desafío.
Cuando se trata de Java, si utiliza Spring API para pruebas de unidades, puede controlar las transacciones a nivel de unidad. En otras palabras, puede ejecutar pruebas unitarias que involucran actualizaciones de bases de datos / inserciones / eliminaciones y deshacer los cambios. Al final de la ejecución, deja todo en la base de datos tal como estaba antes de comenzar la ejecución. Para mí, es lo mejor que puede obtener.
Deberías burlarte del acceso a la base de datos si quieres probar tus clases en unidades. Después de todo, no desea probar la base de datos en una prueba unitaria. Eso sería una prueba de integración.
Resuma las llamadas y luego inserte un simulacro que solo devuelve los datos esperados. Si sus clases no hacen más que ejecutar consultas, puede que incluso no valga la pena probarlas, aunque ...
El libro xUnit Test Patterns describe algunas formas de manejar el código de pruebas unitarias que llega a una base de datos. Estoy de acuerdo con las otras personas que dicen que no quieres hacer esto porque es lento, pero tienes que hacerlo alguna vez, OMI. Es una buena idea burlarse de la conexión db para probar cosas de más alto nivel, pero consulte este libro para obtener sugerencias sobre lo que puede hacer para interactuar con la base de datos real.
Estoy de acuerdo con la primera publicación: el acceso a la base de datos debe eliminarse en una capa DAO que implementa una interfaz. Luego, puede probar su lógica contra una implementación de stub de la capa DAO.
Idealmente, sus objetos deberían ser ignorantes persistentes. Por ejemplo, debería tener una "capa de acceso a datos" a la que haría solicitudes, que devolvería objetos. De esta forma, puede dejar esa parte fuera de las pruebas de su unidad, o probarlas en forma aislada.
Si sus objetos están estrechamente acoplados a su capa de datos, es difícil hacer las pruebas unitarias correctas. la primera parte de la prueba unitaria, es "unidad". Todas las unidades deben poder probarse de forma aislada.
En mis proyectos de c #, uso NHibernate con una capa de datos completamente separada. Mis objetos viven en el modelo de dominio central y se accede desde mi capa de aplicación. La capa de aplicación se comunica con la capa de datos y la capa del modelo de dominio.
La capa de aplicación también se denomina a veces "Capa empresarial".
Si está utilizando PHP, cree un conjunto específico de clases para el ÚNICO acceso a datos. Asegúrese de que sus objetos no tengan idea de cómo se conservan y conecte los dos en sus clases de aplicación.
Otra opción sería usar burla / talones.
La forma más fácil de probar un objeto con acceso a la base de datos es usar ámbitos de transacción.
Por ejemplo:
[Test]
[ExpectedException(typeof(NotFoundException))]
public void DeleteAttendee() {
using(TransactionScope scope = new TransactionScope()) {
Attendee anAttendee = Attendee.Get(3);
anAttendee.Delete();
anAttendee.Save();
//Try reloading. Instance should have been deleted.
Attendee deletedAttendee = Attendee.Get(3);
}
}
Esto revertirá el estado de la base de datos, básicamente como una reversión de transacción para que pueda ejecutar la prueba todas las veces que quiera sin ningún efecto secundario. Hemos utilizado este enfoque con éxito en proyectos grandes. Nuestra construcción tarda un poco en ejecutarse (15 minutos), pero no es horrible tener 1800 pruebas unitarias. Además, si el tiempo de compilación es una preocupación, puede cambiar el proceso de compilación para que tenga varias compilaciones, una para compilar src, otra que luego se active para las pruebas unitarias, análisis de código, empaquetado, etc.
La prueba unitaria de su acceso a la base de datos es bastante fácil si su proyecto tiene una alta cohesión y un acoplamiento flexible en todo. De esta forma puede probar solo las cosas que hace cada clase en particular sin tener que probar todo a la vez.
Por ejemplo, si prueba su clase de interfaz de usuario por unidad, las pruebas que escriba solo deberían intentar verificar que la lógica dentro de la UI funcionó como se esperaba, no la lógica de negocio o la acción de base de datos detrás de esa función.
Si quiere probar el acceso real a la base de datos, en realidad terminará con más pruebas de integración, porque dependerá de la pila de red y de su servidor de base de datos, pero puede verificar que su código SQL haga lo que le pidió. hacer.
El poder oculto de las pruebas unitarias para mí personalmente ha sido que me obliga a diseñar mis aplicaciones de una manera mucho mejor que sin ellas. Esto se debe a que realmente me ayudó a romper con la mentalidad de "esta función debe hacer todo".
Lo siento, no tengo ningún ejemplo de código específico para PHP / Python, pero si quiere ver un ejemplo de .NET, tengo una post que describe una técnica que solía hacer esta misma prueba.
Nunca he hecho esto en PHP y nunca he usado Python, pero lo que quieres hacer es burlar las llamadas a la base de datos. Para hacer eso, puede implementar alguna IoC ya sea una herramienta de terceros o usted mismo, luego puede implementar alguna versión simulada de la persona que llama a la base de datos, que es donde controlará el resultado de esa llamada falsa.
Se puede realizar una forma simple de IoC simplemente codificando en Interfaces. Esto requiere que haya algún tipo de orientación de objeto en tu código, por lo que puede no aplicarse a lo que estás haciendo (digo que ya que todo lo que tengo que decir es tu mención de PHP y Python)
Espero que sea útil, si nada más tienes algunos términos para buscar ahora.
Opciones que tienes:
- Escriba una secuencia de comandos que borrará la base de datos antes de comenzar las pruebas de la unidad, luego llene la base de datos con un conjunto predefinido de datos y ejecute las pruebas. También puede hacer eso antes de cada prueba: será lento, pero menos propenso a errores.
Inyectar la base de datos. (Ejemplo en pseudo-Java, pero se aplica a todos los OO-languages)
class Database { public Result query(String query) {... real db here ...} }
class MockDatabase extends Database { public Result query(String query) { return "mock result"; } }
class ObjectThatUsesDB { public ObjectThatUsesDB(Database db) { this.database = db; } }
ahora en producción utiliza una base de datos normal y para todas las pruebas, solo debe inyectar la base de datos simulada que puede crear ad hoc.- No use DB en absoluto a lo largo de la mayor parte del código (de todos modos, es una mala práctica). Cree un objeto de "base de datos" que en lugar de devolver los resultados devolverá objetos normales (es decir, devolverá el
User
lugar de una tupla{name: "marcin", password: "blah"}
) escribirá todas sus pruebas con objetos reales construidos ad hoc y escriba una prueba grande que dependa de una base de datos que asegure que esta conversión funcione correctamente.
Por supuesto, estos enfoques no son mutuamente exclusivos y puede mezclarlos y combinarlos según lo necesite.
Podría usar marcos de burla para abstraer el motor de la base de datos. No sé si PHP / Python obtuvo algo más que para los idiomas escritos (C #, Java, etc.) hay muchas opciones
También depende de cómo haya diseñado el código de acceso a la base de datos, ya que algunos diseños son más fáciles de probar por unidad que otros como los mencionados en las publicaciones anteriores.
Por lo general, trato de dividir mis pruebas entre probar los objetos (y ORM, si hay alguno) y probar el db. Pruebo el lado del objeto de las cosas burlando las llamadas de acceso a datos, mientras que pruebo el lado db de las cosas probando las interacciones del objeto con el db que, en mi experiencia, suele ser bastante limitado.
Solía frustrarme con las pruebas de unidad de escritura hasta que comencé a burlarme de la parte de acceso a los datos, así que no tuve que crear una prueba de DB o generar datos de prueba sobre la marcha. Al burlarse de los datos, puede generarlos en tiempo de ejecución y asegurarse de que sus objetos funcionen correctamente con las entradas conocidas.
Sugeriría burlarse de sus llamadas a la base de datos. Los mocks son básicamente objetos que se parecen al objeto sobre el que intenta llamar un método, en el sentido de que tienen las mismas propiedades, métodos, etc. disponibles para quien llama. Pero en lugar de realizar cualquier acción que estén programados para hacer cuando se llama a un método en particular, lo omite por completo, y solo devuelve un resultado. Ese resultado generalmente lo define usted por adelantado.
Para configurar tus objetos para burlarse, probablemente necesites usar algún tipo de inversión de patrón de inyección de control / dependencia, como en el siguiente pseudo-código:
class Bar
{
private FooDataProvider _dataProvider;
public instantiate(FooDataProvider dataProvider) {
_dataProvider = dataProvider;
}
public getAllFoos() {
// instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
return _dataProvider.GetAllFoos();
}
}
class FooDataProvider
{
public Foo[] GetAllFoos() {
return Foo.GetAll();
}
}
Ahora, en su prueba de unidad, crea un simulacro de FooDataProvider, que le permite llamar al método GetAllFoos sin tener que acceder a la base de datos.
class BarTests
{
public TestGetAllFoos() {
// here we set up our mock FooDataProvider
mockRepository = MockingFramework.new()
mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);
// create a new array of Foo objects
testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}
// the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
// instead of calling to the database and returning whatever is in there
// ExpectCallTo and Returns are methods provided by our imaginary mocking framework
ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)
// now begins our actual unit test
testBar = new Bar(mockFooDataProvider)
baz = testBar.GetAllFoos()
// baz should now equal the testFooArray object we created earlier
Assert.AreEqual(3, baz.length)
}
}
Un escenario burlón común, en pocas palabras. Por supuesto, es probable que desee probar también sus llamadas reales a la base de datos, para lo cual tendrá que acceder a la base de datos.
Tal vez pueda darle una idea de nuestra experiencia cuando comenzamos a examinar las pruebas unitarias de nuestro proceso de nivel medio que incluía un montón de operaciones sql de "lógica de negocios".
Primero creamos una capa de abstracción que nos permitía "insertar" cualquier conexión de base de datos razonable (en nuestro caso, simplemente admitimos una única conexión de tipo ODBC).
Una vez que esto estuvo en su lugar, pudimos hacer algo como esto en nuestro código (trabajamos en C ++, pero estoy seguro de que se entiende):
GetDatabase (). ExecuteSQL ("INSERT INTO foo (blah, blah)")
En el tiempo de ejecución normal, GetDatabase () devolvería un objeto que alimenta todos nuestros sql (incluidas las consultas), a través de ODBC directamente a la base de datos.
Luego comenzamos a buscar en las bases de datos en memoria: lo mejor de lejos parece ser SQLite. ( http://www.sqlite.org/index.html ). Es notablemente simple de configurar y usar, y nos permitió crear subclase e invalidar GetDatabase () para reenviar SQL a una base de datos en memoria que se creó y destruyó para cada prueba realizada.
Todavía estamos en las primeras etapas de esto, pero se ve bien hasta ahora, sin embargo, tenemos que asegurarnos de crear tablas que sean necesarias y llenarlas con datos de prueba. Sin embargo, hemos reducido la carga de trabajo aquí creando un conjunto genérico de funciones de ayuda que pueden hacer mucho de esto por nosotros.
En general, ha ayudado inmensamente con nuestro proceso TDD, ya que realizar cambios aparentemente inocuos para corregir ciertos errores puede tener efectos bastante extraños en otras áreas (difíciles de detectar) de su sistema, debido a la naturaleza misma de sql / databases.
Obviamente, nuestras experiencias se han centrado en un entorno de desarrollo en C ++, sin embargo, estoy seguro de que quizás pueda obtener algo similar trabajando en PHP / Python.
Espero que esto ayude.