2009-08-25 7 views
11

Estoy usando SqlAlchemy, una biblioteca ORM de python. Y solía acceder a la base de datos directamente desde la capa empresarial directamente llamando a la API SqlAlchemy.¿Cómo organizar una capa de acceso a la base de datos?

Pero luego descubrí que eso causaría demasiado tiempo para ejecutar todos mis casos de prueba y ahora creo que debería crear una capa de acceso a bases de datos, así puedo usar objetos simulados durante la prueba en lugar de acceder directamente a la base de datos.

creo que hay 2 opciones para hacerlo:

  1. uso de una sola clase que contiene una conexión de base de datos y muchos métodos como addUser/deluser/updateUser, addBook/delBook/updateBook. Pero esto significa que esta clase será muy grande.

  2. Otro enfoque es crear diferentes clases de administrador como "UserManager", "BookManager". Pero eso significa que tengo que pasar una lista de gerentes a la capa Business, lo que parece un poco engorroso.

¿Cómo se organizará una capa de base de datos?

Respuesta

5

¡Esa es una buena pregunta!
El problema no es trivial, y puede requerir varios enfoques para abordarlo. Por ejemplo:

  1. organizar el código, por lo que se puede probar la mayor parte de la lógica de la aplicación sin tener acceso a la base de datos. Esto significa que cada clase tendrá métodos para acceder a los datos y métodos para procesarlos, y los segundos se pueden probar fácilmente.
  2. Cuando necesite probar el acceso a la base de datos, puede usar un proxy (así, como solución # 1); se puede considerar como un motor para SqlAlchemy o como un reemplazo directo para la SA. En ambos casos, es posible que desee pensar en un self initializing fake.
  3. Si el código no involucra procedimientos almacenados, piense en usar bases de datos en memoria, como dice Lennart (incluso si en este caso, llamarlo "prueba de unidad" puede sonar un poco extraño).

Sin embargo, desde mi experiencia, todo es bastante fácil en la palabra, y luego se cae abruptamente cuando vas al campo. Por ejemplo, ¿qué hacer cuando la mayor parte de la lógica está en las declaraciones SQL? ¿Qué pasa si el acceso a los datos está intercalado estrictamente con su procesamiento? En ocasiones, es posible que pueda refactorizar, a veces (especialmente con aplicaciones grandes y heredadas) no.

Al final, creo que es sobre todo una cuestión de mentalidad.
Si cree que necesita pruebas de unidad, y necesita que funcionen rápidamente, entonces diseñe su aplicación de una cierta manera, que permita una prueba de unidad más sencilla.
Desafortunadamente, esto no siempre es cierto (muchas personas ven las pruebas unitarias como algo que puede ejecutarse durante la noche, así que el tiempo no es un problema), y obtienes algo que no será realmente comprobable por la unidad.

2

Me gustaría configurar una conexión de base de datos durante la prueba que se conecta a una base de datos en memoria en su lugar. De este modo:

sqlite_memory_db = create_engine('sqlite://') 

Eso será más o menos tan rápido como usted puede conseguir, que está Tampoco se conecta a una base de datos real, pero sólo uno temporal en la memoria, por lo que no tiene que preocuparse acerca de la cambios hechos por las pruebas que quedan después de la prueba, etc. Y no tiene que burlarse de nada.

+0

Hola, porque algunos de mis colegas insisten en que debemos usar el procedimiento de almacenamiento y no tengo control para eso, por lo que sqlite no es una opción posible para mí. – ablmf

+0

BTW: sqlite no es compatible con el procedimiento de almacenamiento. – ablmf

+0

Hm. Esto significa que tendrá que burlarse de partes importantes del código (los procedimientos almacenados). Eso hará que las pruebas sean mucho menos útiles. Dificil situacion. –

0

SQLAlchemy tiene algunas funciones para making mocking easier - tal vez eso sería más fácil que tratar de reescribir secciones completas de su proyecto?

+0

Gracias @brool, pero ese enlace está roto ahora. :( –

2

Una forma de capturar las modificaciones a la base de datos, es utilizar el mecanismo de extensión sesión SQLAlchemy y rubores interceptar a la base de datos usando algo como esto:

from sqlalchemy.orm.attributes import instance_state 
from sqlalchemy.orm import SessionExtension 

class MockExtension(SessionExtension): 
    def __init__(self): 
     self.clear() 

    def clear(self): 
     self.updates = set() 
     self.inserts = set() 
     self.deletes = set() 

    def before_flush(self, session, flush_context, instances): 
     for obj in session.dirty: 
      self.updates.add(obj) 
      state = instance_state(obj) 
      state.commit_all({}) 
      session.identity_map._mutable_attrs.discard(state) 
      session.identity_map._modified.discard(state) 

     for obj in session.deleted: 
      self.deletes.add(obj) 
      session.expunge(obj) 

     self.inserts.update(session.new) 
     session._new = {} 

Luego de pruebas puede configurar su sesión con ese simulacro y mira si coincide con tus expectativas.

mock = MockExtension() 
Session = sessionmaker(extension=[mock], expire_on_commit=False) 

def do_something(attr): 
    session = Session() 
    obj = session.query(Cls).first() 
    obj.attr = attr 
    session.commit() 

def test_something(): 
    mock.clear() 
    do_something('foobar') 
    assert len(mock.updates) == 1 
    updated_obj = mock.updates.pop() 
    assert updated_obj.attr == 'foobar' 

Pero usted querrá hacer al menos algunas pruebas con una base de datos de todas formas, ya que tendrá al menos quiere saber si sus consultas funcionan como se esperaba. Y tenga en cuenta que también puede tener modificaciones en la base de datos a través del session.update(), .delete() y .execute().

Cuestiones relacionadas