2009-08-25 14 views
11

Sto usando SqlAlchemy, una libreria ORM python. E ho usato per accedere direttamente al database direttamente dal livello aziendale chiamando l'API SqlAlchemy.Come organizzare un livello di accesso alla base dati?

Ma poi ho scoperto che avrebbe causato troppo tempo per eseguire tutti i miei casi di test e ora penso che forse dovrei creare un livello di accesso DB, così posso usare gli oggetti mock durante il test invece del database di accesso direttamente.

Penso che ci sono 2 scelte per farlo:

  1. uso di una singola classe che contiene una connessione DB e molti metodi come addUser/deluser/UpdateUser, addBook/delBook/updateBook. Ma questo significa che questa classe sarà molto grande.

  2. Un altro approccio è creare diverse classi di gestori come "UserManager", "BookManager". Ma ciò significa che devo passare un elenco di manager al livello Business, che sembra un po 'ingombrante.

Come si organizza un livello di database?

risposta

5

Questa è una buona domanda!
Il problema non è banale e potrebbe richiedere diversi approcci per affrontarlo. Ad esempio:

  1. Organizzare il codice, in modo da poter testare gran parte della logica dell'applicazione senza accedere al database. Ciò significa che ogni classe avrà metodi per accedere ai dati e metodi per elaborarli, mentre i secondi potrebbero essere testati facilmente.
  2. Quando è necessario verificare l'accesso al database, è possibile utilizzare un proxy (quindi, come la soluzione n. 1); lo si può considerare come un motore per SqlAlchemy o come un rimpiazzo sostitutivo per la SA. In entrambi i casi, potresti pensare a un self initializing fake.
  3. Se il codice non comporta stored procedure, pensa di utilizzare i database in memoria, come dice Lennart (anche se in questo caso chiamarlo "unit test" potrebbe sembrare un po 'strano!).

Tuttavia, dalla mia esperienza, tutto è abbastanza semplice a parole, e poi cade bruscamente quando si va sul campo. Ad esempio, cosa fare quando la maggior parte della logica è nelle istruzioni SQL? Cosa succede se l'accesso ai dati è strettamente intercalato con la sua elaborazione? A volte potresti essere in grado di refactoring, a volte (soprattutto con applicazioni grandi e legacy) no.

Alla fine, penso che sia principalmente una questione di mindset.
Se pensate di aver bisogno di test unitari, e avete bisogno di farli funzionare velocemente, progettate la vostra applicazione in un certo modo, che consente una più facile verifica dell'unità.
Sfortunatamente, questo non è sempre vero (molte persone vedono i test unitari come qualcosa che può essere eseguito durante la notte, quindi il tempo non è un problema) e si ottiene qualcosa che non sarà realmente testabile da unità.

2

Avrei impostato una connessione al database durante il test che si collega invece a un database in memoria. In questo modo:

sqlite_memory_db = create_engine('sqlite://') 

che sarà più o meno veloce come si può ottenere, si sono, inoltre, non si collega ad un database vero e proprio, ma solo una temporanea in memoria, in modo da non devi preoccuparti per il i cambiamenti fatti dai test rimanenti dopo il test, ecc. E non devi prendere in giro nulla.

+0

Ciao, perché alcuni dei miei colleghi insistono sul fatto che dovremmo usare la procedura di archiviazione e non ho alcun controllo su di esso, quindi sqlite non è una scelta possibile per me. – ablmf

+0

BTW: sqlite non supporta la procedura di archiviazione. – ablmf

+0

Hm. Ciò significa che dovrai prendere in giro parti significative del codice (le stored procedure). Questo renderà i test molto meno utili. Situazione difficile. –

0

SQLAlchemy dispone di alcune strutture per making mocking easier - forse sarebbe più facile che provare a riscrivere intere sezioni del progetto?

+0

Grazie a @brool, ma quel link è rotto ora. :( –

2

Un modo per catturare le modifiche al database, è quello di utilizzare il meccanismo SQLAlchemy estensione sessione e vampate di intercettare al database utilizzando qualcosa di simile:

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 = {} 

Poi per i test è possibile configurare la sessione con quel finto e vedi se corrisponde alle tue aspettative.

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' 

Ma si vorrà fare almeno alcuni test con un database in ogni caso perché ti atleast vuole sapere se le query funzionano come previsto. E tieni presente che puoi anche apportare modifiche al database tramite session.update(), .delete() e .execute().

Problemi correlati