State-enforcement interfaces

Questo post lo scrivo in inglese, visto che è un’osservazione che parte da un post di Andrea, e il suo blog è in inglese.

Andrea raises the issue of objects whose state is not always valid for all their methods.
I’ll take a classic example where you must first invoke method 1 in order to be able to invoke method 2 :

connection.open(“url.of.server”);
connection.send(“foo”);

If you don’t open the connection first the send method will throw an exception.
This is obviously bad, as the user cannot simply presume that, since the object is available, he can use it. This requires extra-attention on the user side, and, unless the object’s creation is local, this extra-attention, even if applied, will translate into ifs, redundant method calls (calling open before every send, just to be sure) and so on.

The solution to this could be opening the connection at the moment of creation :

Connection connection = new Connection(“url.of.server”);
connection.send(“foo”);

This way, once you have the connection object in your hands, you know it will work.

Pity, though, that often it is not viable to open the connection at the moment of creating the object, for a thousand reasons, one for every flavor : optimization, security, release granularity (creating an object at every call forces us to deal with concrete classes or factories) and many more.

This is true for all sorts of objects which are intrinsically multi-state and whose states cannot satisfy all methods.

Careful here, most cases of objects which happen to be partially initialized are just the result of faulty design, not to forget the excellent practice of savage javabeaning and zero-argument constructors.

So, when all else fails, I’ve found that I may prevent most mishaps on the user side by implementing a nice idiom I first saw in jMock.

In this idiom I basically reckon that, since not all methods are supported in a given state of the object, the object should be used only through an interface that offers just those methods that are supported by the current state.

In the previous example I would define the Connection class this way :

public class Connection implements NewConnection, OpenConnection {

    public static NewConnection create(String url) {
        return new Connection(url);
    }

    private final String url;

    private Connection(String url) {
        this.url = url;
    }

    public OpenConnection open() {
        //Do stuff with the url and open the connection
        return this;
    }

    public void send(String message) {
        //Send the message
    }

}

The two interfaces look like this :

public interface NewConnection {

    OpenConnection open();

}

public interface OpenConnection {

    void send(String message);

}

Why the factory? Because I want to enforce the fact that when I create a new connection it is a “NewConnection”, a use sample :

NewConnection newConnection = Connection.creaet(“url.of.server”);
newConnection.open().send(message);

Having learned this idiom by looking at jMock, I have yet to find the paper where this idiom/pattern is defined and thus I’ve invented a name for it : “state-enforcement interfaces”, as it allows the developer of a component to guide the user along valid sequences of method calls.

An object may implement a lot of interfaces, with each interface defining one or more methods that transition the object to a different interface. If we make sure that all methods return the object itself this idiom is a very readable yet compact way of performing complex conditional initializations, if we then forget about the classic “verb = method, noun = object” rule we can make it really look like a language :

NewConnection newConnection = CreateA.http().connection().to(“url.of.server”).onPort(80);
ListeningServer server = CreateA.ftp().server().listeningOnPort(6945).allowing(60).users().every(30).minutes();

The best thing is that this little language has its syntax enforced by the various interfaces and their transitions.

Alla faccia della convenzione di Ginevra

L’altro giorno si stava scegliendo il regalo per il mio compleanno nell’unico posto dove si può essere certi di trovare un regalo per cui valga la pena di pagare le tasse d’importazione.
Al momento del checkout, le scimmie ben ammaestrate di ThinkGeek ci hanno messo davanti agli occhi dei meravigliosi microbi pelouche.

Ovviamente, per quanto si stesse lavorando al -mio- regalo, questi piccoli batuffoli morbidi dai nomi caratteristici (Syphilis, Ebola…) sono subito diventati un regalo per lei, e, come negarlo, nemmeno a me dispiacciono.

Detto fatto, dopo l’utilissimo termometro a infrarossi (chi non ha mai avuto la curiosità di misurare la temperatura di una lampadina?), abbiamo aggiunto al carrello anche una selezione dei suddetti piccoli mostri estremamente “cute”.

Un acquisto innocente, no?

Non per l’addetta UPS alla dogana merci di Milano che mi ha telefonato con tono preoccupato l’altra sera durante il meeting del jug torino :
– Buonasera, c’è qui un pacco per lei, ma avrei bisogno di sapere esattamente cosa c’è dentro…
– Ah si, è per il valore per le tasse d’importazione
– Si si, ma avremmo soprattutto bisogno di sapere cosa c’è dentro…
– Certo certo, dunque… un tappetino per mouse, un termometro a infrarossi e mi pare nient’altro
(dei morbidi e innocenti pelouche mi ero completamente dimenticato, ma a quel punto la signorina si è fatta avanti)
– Ma qui c’è scritto microbi, sifilide credo, mononucleosi!
– Ah si! Sono dei piccoli pelouche!
– Pelouche?!
– Si, con la forma di virus e batteri di famose malattie.
– Ah! Capisco! – la voce molto sollevata – ecco perchè dice “plush”!
– Ma cos’è… si era spaventata? Batteri in una scatola di cartone di ThinkGeek?
– Sa com’è, qui arriva un sacco di roba strana, insomma, un termometro e poi tutti questi nomi con scritto “plush microbe” a fianco.

E’ andata bene che mi hanno trovato subito al telefono, se no magari chiamavano la squadra bio-hazard per vaporizzare i miei pelouche e l’antiterrorismo per arrestare me!

A volte, facendo acquisti su siti troppo originali, si fa prendere un colpo a quelli della dogana… ben gli sta… 40 € di tasse, i maledetti, la prossima volta prendo Ebola e Peste Nera, tanto per ridere.

P.S. La temperatura della lampadina del mio comodino è 78 °C

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

TDD-Backfire

E’ mezzanotte e un quarto e ho trovato il baco.
Ho iniziato alle 10.30.
3 pomodori buttati !

Ecco perchè :

Oggi, verso il finire della giornata di lavoro, il mio compagno di pair doveva andarsene a casa, io volevo vedere spuntare il risultato, insomma, integrare tutto prima di andarmene.
Gli ho detto che mi facevo un ultimo pomodoro da solo e ho persino scherzato : “E così riuscirò a inserire tre bug!”

Tre, no, uno, si, ma è bastato.

Per poter vedere girare tutto bisognava completare il task : “Un test può avere molti risultati”
In particolare noi prima avevamo una lista di possibili controlli, ognuno di questi detto “Check”. Un Check lo si crea passandogli la descrizione del test da effettuare e la condizione che deve essere soddisfatta, pressapoco così :
new Check(“Click on ok”,”the result is 4″)

Il fatto è che ci potrà essere anche un check così :
new Check(“Click on ok”,”the result is 10″)

Il test da effettuare è sempre lo stesso, ma, cambiando altre condizioni, il risultato atteso è diverso.
Noi vogliamo assicurarci che eventuali tests già esistenti non vengano di fatto duplicati al momento della creazione di un nuovo Check che differisce solo nel risultato.
In altre parole vogliamo che ogni frase di test possa avere molteplici risultati, ogni coppia test-risultato diventa poi il nostro check.

Niente di più facile, le factory esistono per questo.
L’idea è questa, avere una classe DomainTest, che si istanzia così : new DomainTest(“Click on ok”);
poi con il test si possono creare tanti bei Check :
test.createCheck(“the result is 4”);
test.createCheck(“the result is 10”);

Lindo no?

Il TDD è sacro, quindi mi preoccupo di come testare la nuova funzionalità. Mi guardo attorno e noto questa e altre suite di test simili :
…..
Check check1 = new Check(“test 1″,”result 1”);
Check check2 = new Check(“test 2″,”result 2”);
Check check3 = new Check(“test 3″,”result 3”);

…..fai questo, fai quello…..

assertEquals(check1,action.getNextCheck());
assertEquals(check2,action.getNextCheck());
assertEquals(check3,action.getNextCheck());
assertNull(action.getNextCheck()); // End of checks is reported with a null, which is fair for Struts 2
…..

E mi dico : “Vorrai mica che con tutte queste inizializzazioni di Check mi metta a scrivere dei test aggiuntivi che controllino il nuovo modo di inizializzare check? Se qualcosa va storto di sicuro fallirà almeno uno di questi test”
Si apprezzi la furbizia; stavo aggiungendo una funzionalità, cosa che avrei dovuto fare solo in caso di test rosso, ma visto che avevo tanti bei test verdi che avrebbero usato quella funzionalità trasversalmente, ho deciso di concedermi di invertire il processo.

Passo quindi all’implementazione : creo una classe Test, poi vado su Check, prendo il costruttore e gli do un bel “introduce factory” con l’idea di spostarla poi in Test.
Eclipse mi sputa in un occhio, ogni tanto gli prende… vabbè, lo faccio a manina.
Metto a default il costruttore di Check, gli cambio il primo parametro da String a Test :
Check(DomainTest test, String result) …..

Subito balzano fuori dei bei rossi di compilazione, tutti test basati sui Check sono rotti, vado nella suite suddetta e ci scrivo la nuova forma di creazione dei check :
Check check1 = new Test(“test 1”).createCheck(“result 1”);
Manca il metodo createCheck, ctrl + 1 ed Eclipse me lo crea al volo, vuoto, poi passo a sostituire tutti i “new Check” come avevo fatto col primo.
E’ lunga quando il refactoring di Eclipse ti lascia a terra.

A quel punto era davvero tardi, convinto di aver finito lancio tutti i test, acceptance inclusa (in HtmlUnit) pronto ad andarmene a casa.
Rossi… ma solo 4 e solo tra quelli di acceptance, quelli che testavano le azioni che usavano i check sono tutti ok.

Bizzarro.. la modifica è sui Check, perchè non sono falliti i test unitari che usano quelli?

Scappo a casa.

Col tarlo nella testa alle 10 passate riapro il laptop… che cosa avevo combinato?

Guardo i fallimenti, e mi parlano di descrizioni di Check mancanti, ma, ovviamente, visto che i tests sui Check erano verdi l’ultima cosa che penso è di rileggere quelli. Inizio invece a farmi delle paranoie sull’inizializzazione delle actions con Spring, sulla sessione, se per caso si perdevano degli oggetti dentro al modello, se i nuovi checks venivano ben inseriti dalle azioni… ho persino aggiunto nuova acceptance per assicurarmi che le jsp facessero davvero quello che dicevano.

Per ognuna di queste paranoie ho scritto un test, e passavano tutti.

Finalmente, praticamente per sbaglio, giusto perchè tutte le cose ingarbugliate passavano senza problemi, ho aggiunto questo :
@Test
public void firstCheckExists() {
assertNotNull(action.getNextCheck());
}

Perchè dico “per sbaglio”… perchè quel controllo io lo consideravo implicito nelle suite di tests come quella che ho riassunto sopra.
L’ho aggiunto così.. attendendomi il verde, quasi per scaramanzìa.

In tutte le mie paranoie avevo sperato di vedere spuntare il rosso, ed era sempre tutto disperatamente verde.
E invece quello, due secondi di scrittura, un picosecondo di esecuzione, in cui davo per scontato il verde è diventato subito rosso.

Il maledetto Check era nullo. Ma nullo nullo.

Un po’ come un assiduo camminatore che inciampa nel giardino di casa propria e vuole rialzarsi mostrando di sapere ancora camminare; invece di andare subito ad aprire e modificare il metodo che ormai sapevo essere la causa di tutto, ho scritto il test che avrei dovuto scrivere da subito :
@Test
public void domainTestCreatesNewCheckWithGivenResult() {
DomainTest test1 = new DomainTest(“test 1”);
Check check1 = test1.createCheck(“result 1”);
assertNotNull(check1);
assertEquals(“test 1 : result 1”,check1.toString());
}

circa 20 secondi per scriverlo. Rosso. Ovviamente.

Eclipse, col ctrl + 1, aveva creato sì il metodo createCheck e lo aveva creato così :
public Check createCheck(String result) {
// TODO stub blah blah
return null;
}

Poi, dovendo andare ad aggiornare tutti le istanziazioni a mano mi ero dimenticato di implementare quel metodo. Non c’era il navigatore.

Ma perchè i miei test che usavano Check continuavano a passare?

Perchè la verità è che nessun test assicura la correttezza di una feature a meno che non sia stato creato proprio per quella feature. Sperare nel “cross-testing” significa tramutare la propria suite di tests, sviluppata con rigore in settimane di lavoro, da strumento di grande qualità, a fonte di false sicurezze, che azzoppano il processo di analisi.

I tests di cui sopra passavano tutti grazie a questo : assertEquals(null,null) e assertNull(null)

I costruttori non possono mai ritornare null, al massimo sganciano eccezioni, mentre le factory possono farlo tranquillamente. Il refactoring era incompleto, la modifica non era invariante del comportamento, e amen, ma il fatto è che mancavano quattro linee di test, quello è l’errore, punto.

Ci vuole così poco, un one-liner non testato… pensare che giusto qualche tempo fa avevo giusto risposto a un’osservazione con una frase molto Yoda : “Sono le responsabilità semplici che bisogna ricordarsi di testare, le altre te lo ricordano da sole”

La domanda, legittima, è anche : ma quando ho creato “Check” non ho testato i suoi metodi principali?

Già… qui bisogna ringraziare tanto la pratica del javabeaning selvaggio promosso da buona parte dei framework web per Java. Check di fatto è un elemento della Gui, ma invece di avere un metodo con la responsabilità di renderizzarlo, questa azione viene delegata alle jsp, così Check è solo un data-holder triste, che fa solo set e get dei suoi attributi.

I data-holder sono sbagliati non solo per i danni che causano alla divisione delle responsabilità tanto cara al design OO, ma anche perchè, subdoli, tendono a non farsi testare. In fin dei conti, se non hanno responsabilità c’è poco da testare.

Un ultimo appunto : quei “TODO” che mette eclipse nemmeno li vedo, forse è meglio passare i commenti degli stub a “FIXME” e tenere aperta la view.

Comments

ok, so che per te è stata una faticaccia, ma ne è uscito un post utile per noi lettori :)
Posted by riffraff on 05/06/2007 11:18:24 PM