python unit-testing mocking sqlalchemy mox

Python SQLAlchemy: simulando el método "desc" de un atributo modelo



unit-testing mocking (1)

No creo que realmente esté obteniendo muchos beneficios al usar simulaciones para probar sus consultas. Las pruebas deberían 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ás probando la 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.

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 el 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 así como:

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 burlarme 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 para objetos falsos, mi prueba de unidad actual se ve algo así 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 funciona, aunque se siente feo y no estoy seguro de por qué falla sin el "AndReturn (''test'')" pieza de "Post.date.desc (). AndReturn (''test'')"