2011-02-25 7 views
7

En mi aplicación, hay una clase para cada modelo que contiene consultas de uso común (supongo que es algo así como un "Repositorio" en lenguaje DDD). Cada una de estas clases se pasa al objeto de sesión SQLAlchemy para crear consultas durante la construcción. Tengo un poco de dificultad para determinar la mejor forma de afirmar que ciertas consultas se están ejecutando en mis pruebas unitarias. Utilizando el ejemplo de blog ubicuo, digamos que tengo un modelo "Publicar" con columnas y atributos "fecha" y "contenido". También tengo un "PostRepository" con el método "find_latest" que se supone que consulta todas las publicaciones en orden descendente por "fecha". Se ve algo como:Python SQLAlchemy: simulando el método "desc" de un atributo de modelo

from myapp.models import Post 

class PostRepository(object): 
    def __init__(self, session): 
     self._s = session 

    def find_latest(self): 
     return self._s.query(Post).order_by(Post.date.desc()) 

Tengo problemas para burlarse de la llamada Post.date.desc(). En este momento soy mono parcheando un simulacro de Post.date.desc en mi prueba de unidad, pero creo que es probable que haya un mejor enfoque.

Editar: Estoy usando mox de objetos simulados, mi prueba de la unidad actual se ve algo como:

import unittest 
import mox 

class TestPostRepository(unittest.TestCase): 

    def setUp(self): 
     self._mox = mox.Mox() 

    def _create_session_mock(self): 
     from sqlalchemy.orm.session import Session 
     return self._mox.CreateMock(Session) 

    def _create_query_mock(self): 
     from sqlalchemy.orm.query import Query 
     return self._mox.CreateMock(Query) 

    def _create_desc_mock(self): 
     from myapp.models import Post 
     return self._mox.CreateMock(Post.date.desc) 

    def test_find_latest(self): 
     from myapp.models.repositories import PostRepository 
     from myapp.models import Post 

     expected_result = 'test' 

     session_mock = self._create_session_mock() 
     query_mock = self._create_query_mock() 
     desc_mock = self._create_desc_mock() 

     # Monkey patch 
     tmp = Post.date.desc 
     Post.date.desc = desc_mock 

     session_mock.query(Post).AndReturn(query_mock) 
     query_mock.order_by(Post.date.desc().AndReturn('test')).AndReturn(query_mock) 
     query_mock.offset(0).AndReturn(query_mock) 
     query_mock.limit(10).AndReturn(expected_result) 

     self._mox.ReplayAll() 
     r = PostRepository(session_mock) 

     result = r.find_latest() 
     self._mox.VerifyAll() 

     self.assertEquals(expected_result, result) 

     Post.date.desc = tmp 

Esto trabajo, aunque se siente feo y no estoy seguro de por qué no funciona sin la "AndReturn ('prueba') "pieza de" Post.date.desc(). AndReturn ('prueba') "

Respuesta

13

No creo que realmente esté obteniendo muchos beneficios al usar simulaciones para probar sus consultas. Las pruebas deben probar la lógica del código, no la implementación . Una solución mejor sería crear una nueva base de datos, agregarle algunos objetos, ejecutar la consulta en esa base de datos y determinar si está obteniendo los resultados correctos. Por ejemplo:


# Create the engine. This starts a fresh database 
engine = create_engine('sqlite://') 
# Fills the database with the tables needed. 
# If you use declarative, then the metadata for your tables can be found using Base.metadata 
metadata.create_all(engine) 
# Create a session to this database 
session = sessionmaker(bind=engine)() 

# Create some posts using the session and commit them 
... 

# Test your repository object... 
repo = PostRepository(session) 
results = repo.find_latest() 

# Run your assertions of results 
... 

Ahora, en realidad está probando el lógica del código. Esto significa que puede cambiar la implementación de su método, pero siempre que la consulta funcione correctamente, las pruebas deben pasar. Si lo desea, puede escribir este método como una consulta que obtiene todos los objetos, luego divide la lista resultante. La prueba pasaría, como debería. Más adelante, podría cambiar la implementación para ejecutar la consulta utilizando las API de expresión SA, y la prueba pasaría.

Una cosa a tener en cuenta es que puede tener problemas con el comportamiento de sqlite de forma diferente a otro tipo de base de datos. El uso de sqlite en la memoria le proporciona pruebas rápidas, pero si quiere tomar en serio estas pruebas, probablemente también desee ejecutarlas en el mismo tipo de base de datos que utilizará en la producción.

+0

Eso tiene mucho sentido, estaba demasiado pendiente de los detalles del código. Gracias por la visión. –

+5

Lo que está diciendo es que en lugar de hacer una prueba unitaria (una unidad de lógica), realice una prueba de integración (con la base de datos). Es un enfoque válido, y posiblemente el más sensato para un ORM, sin embargo, la realización de estas pruebas sufre una melodía de una o dos magnitudes. ¿Derecha? –

+1

Tiene razón al escribir una prueba (ya sea que desee llamarla prueba de "unidad" o prueba de "integración") que de alguna manera accederá a un recurso más lento (ya sea una unidad de disco, servicio externo o base de datos) significará una prueba más lenta que una que no tiene acceso a dicho recurso. –

Cuestiones relacionadas