TDD sul DB

Il mio personale approccio allo sviluppo dell’interazione tra dominio e DB è sempre stato di usare le stringhe degli statement per tutte le asserzioni dei test unitari, lasciando la vera interazione col DB al momento dell’integrazione :

assertEquals(“INSERT INTO my_table (id,external_id,name) VALUES (1,15,pippo)”,mapper.getStatement());

L’inserimento e il ritorno dei dati lo gestisco nascondendo il ResultSet dietro a un adapter con un metodo “getValue” che, dato il nome della colonna, ritorna il valore. Nei test uso un’implementazione a cui si possono settare i valori subito prima dell’esecuzione della select. Al runtime viene usato invece l’adapter che mappa “getValue” a “getString” di ResultSet.

E’ la terza volta che seguo questo approccio e ha un paio di difetti :

  1. riesco a ricreare in TDD tutte le interazioni, ma la creazione dello schema del database non è testata a monte, è testata all’integrazione.
  2. La validità degli statement deve essere testata a mano sul DB di integrazione.

Tempo fa avevo dato un’occhiata a DBUnit e me l’ero segnato tra le cose interessanti, l’ho preso in mano qualche giorno fa, ma mi ha un po’ deluso. Da quello che ho visto è perfetto per riempire di valori un DB su cui poi fare i test, ma non serve per creare il DB.

Inoltre, riempire un DB di test con delle insert non è necessariemente più error-prone o più fastidioso di riempirlo compilando un grosso XML (vedere il tutorial di DBUnit per quello che intendo). Quando sviluppo il supporto ai tests di acceptance è esattamente quello che faccio e non mi ha mai creato problemi, soprattutto visto che gli statement che uso sono tutti elencati nei miei tests.

Quello invece che mi ha sempre disturbato è proprio la creazione dello schema, che di solito richiede gli statements più complessi. Esistono dei simpatici strumenti di creazione dello schema, molto belli, molto grafici, il controllo a valle è dato dal riscontro che alla fine della fiera tutte le mie insert e le mie select riescono a fare il loro mestiere. Ma questo è un processo diretto, test-last, quindi controllo la correttezza, ma non ho i vantaggi di design e immediatezza che ottengo invece testando a monte con strumenti leggeri come JUnit e i suoi fratelli e cugini. Senza contare che l’integrazione è fatta quasi a mano ogni volta (dove quasi a mano significa “con tool simpatico ma non automatico”).

Il DB può anche venir fuori corretto, ma dovrò sempre andare ad eseguire delle operazioni test-last ogni volta che modifico il codice, in un ambiente di lavoro diverso dal resto dello sviluppo (il file sql di creazione del DB), con tool, o, alla meglio, una view diversa dell’IDE.

Si può osservare che, da dottrina, ciò che interagisce con un db, come ciò che in generale non può essere testato in isolamento, non è propriamente materia per i test unitari. Ma sta tutto a rendere isolato qualcosa che ancora non lo è.

Ora, ho deciso che la creazione del mio DB debba essere solidale col mio codice, quindi che sia localizzata nei sorgenti, che il risultato della creazione si rinnovi con la frequenza con cui lancio i miei tests e quindi che ogni modifica ai suddetti test si rifletta direttamente nel db che viene creato.

Non sono certo il primo a usare Hypersonic per avere un DB a disposizione nei miei tests, in particolare ho scelto di usarlo del tutto “in-memory”, senza alcun accesso al file system, e quindi senza alcuna persistenza al di fuori del mio test. In questo modo mi assicuro che il database sia sempre pulito all’inizio di tutti i tests semplicemente chiudendone la connessione alla fine.

Attorno alla connessione a Hypersonic ho poi costruito una serie di oggetti che rappresenteranno il mio schema relazionale.

Il risultato è stato questo, supponendo un test di JUnit 4 :

@Before
public void myDB() {

db = new DB(“my_db”);
Table insurances = Table.create(“insurances”, Column.integer(“id”).notNull());
insurances.add(Column.varchar(“description”));
insurances.add(Column.integer(“firm_id”).notNull());
db.addTable(insurances);

Table firms = Table.create(“firms”, Column.integer(“id”).notNull());
firms.add(Column.varchar(“firm_name”));
db.addTable(firms);

Table customers = Table.create(“custormers”, Column.integer(“id”).notNull());
customers.add(Column.text(“name”));
customers.add(Column.varchar(“family_name”));
customers.add(Column.varchar(“address”));
customers.add(Column.varchar(“mail”));
db.addTable(customers);

}

Qui si ci mettono tutti i tests, nel nostro caso d’uso attuale si tratta per lo più di passare la db.connection() al manager del Data-Mapper (vedere EAA di Fowler).

@After
public void closeDB() {
db.close();
}

Nella creazione del db ho privilegiato la brevità, ma quello che più conta per me è poter definire lo schema incrementalmente mentre sviluppo l’interazione e quindi poterlo rifattorizzare assieme a tutto il resto.

Devo dire che avere il db pronto a comando e la creazione dello schema ad oggetti dritto nella Before ci ha dato già delle soddisfazioni.

Resta da pensare alla questione della creazione del database di integrazione, a quello ci pensa un task di Ant, creato con un amico in un trip di automatizzazione selvaggia, a cui si passa la classe dove trovare l’inizializzazione del db completo, il metodo di inizializzazione e la variabile del db stesso da cui fare il dump degli statement di creazione, così :

< dump class=”com.myapplication.StorageAndRetrievalTest” method=”myDB” variable=”db” file=”dump.sql” / >

Il file che viene generato poi lo si può passare al task ‘sql’ di ant con le coordinate del db di integrazione da generare.

Al momento il sistema ha decisamente i suoi limiti, ma mi ci sono trovato così bene che ho deciso di dargli un nome e metterlo a disposizione, se può servire.

Il nome che avevo scelto inizialmente è aimè già preso, da quei furbi di ThoughtWorks, l’ho scoperto oggi, quando un collega, che aveva visto la dipendenza a questo mini-tool nelle librerie per il testing nel file di maven per il nostro progetto, ha pensato fosse qualcosa di pubblico, e, cercatolo su google, l’ha pure trovato, scaricandosi quindi il jar sbagliato.

Bisogna dire che i ragazzi di ThoughtWorks non sono stati molto originali nel nome… nemmeno io.

Comunque, ho messo il jar del mio tool sul sito del jug Torino, se qualcuno avrà mai le mie stesse fisime sui DB.

Al momento le assert sugli SQL le si scrive a mano, ma pensavo di supportare la creazione delle query e degli update ‘expected’ e quelle per il caricamento dei dati iniziali in qualche modo stupido, ma comunque da codice, così da lasciarle rifattorizzare a eclipse quando modifico lo schema, o viceversa, qualcosa tipo questo :

Query.select(insuracesId).and(insurancesDescription).from(insurances).where(insurancesId).equals(5);

E magari anche, per controllare se la query, applicata all’attuale db, ha dei risultati : query.returnsOneOrMoreValuesFrom(db)

Così almeno quando cambio i nomi alle tabelle non devo aggiornare a mano decine di tests.

Ci devo pensare, se ho tempo lo faccio.

Comments

Bello! Grazie :) Scarico subito il jar e ci smandruppo la settimana prossima ;)
Posted by fabiobeta on 05/17/2007 07:48:35 AM

Advertisements

One thought on “TDD sul DB

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s