venerdì 29 marzo 2013

EJB 3.1 - BDD con Cucumber e OpenEJB

Scopo di questo post è di produrre una breve guida pratica in taliano (scarseggiano) su come introdurre il Beahviour Driven Development (BDD) nei progetti basati su EE6: nello specifico un progetto basato su EJB3.1 che sfrutta il CDI (Context Dependency Injection) funzionalità introdotta in ee6 che implementa il pattern del Dependency Injection.



Dato che il BDD è basato sul Test Driven Development (TDD) partiamo un attimo da quest'ultima metodologia (impiegata nella metodologie di sviluppo Agile quali Scrum, Lean ecc. In particolare nella Extreme Programming o brevemente XP  ). Come dice il nome stesso è una metodologia di sviluppo software guidata dai test.

Ossia lo scopo di questa metodologia è quella di scrivere prima i test  delle varie componenti del software per poi sviluppare in modo da raggiungere il superamento di tutti i test.

E' quindi basata sul motto Red-Green-Refactor: motto che sintetizza e descrive gli step necessari da fare per utilizzare il TDD.

Nel primo step Red (semaforo rosso) avremo  una situazione in cui i test sono tutti necessariamente falliti (ancora ci sono solo i test non è stato sviluppato il software). Si parte quindi con lo sviluppo affinchè si arrivi al secondo step Green (semaforo verde) ossia tutti i test superati. Si passa quindi allo step Refactor ossia fase in cui senza perdere lo stato verde si ristruttura il codice in modo da renderlo più performante e manutenibile.

Il BDD estende il TDD e porta in se le nozioni del Domain Driven Design (altra metodologia di sviluppo). Beahvior significa comportamento quindi è una metodologia di sviluppo guidata dal comportamento.

Il comportamento non è altro che uno dei modi in cui può agire o reagire una funzionalità offerta dal software.
Cercò di essere più chiaro con un esempio.
Parliamo di un software gestionale fra i servizi offerti (Domains) c'è la gestione dell'anagrafica dei clienti.  Tra le funzionalità di tale servizio ci sarà inserimento di un contatto, ricerca di un contatto, ordinamento della ricerca, modifica di un contatto, sua eliminazione.
Ma l'inserimento di un contatto può avere diversi comportamenti: ossia il contatto non c'è quindi viene archiviato oppure il contatto già c'è quindi viene notificato un messaggio la sua esistenza.

Quindi il comportamento non è altro che un possibile "scenario" di una "funzionalità".

Ora perchè estente il TDD?
Mantenendo sempre il motto del Red-Green-Refactor vengono preparati casi di test basati sui comportamenti delle varie funzionalità e non sulle singole componenti del codice (requisiti tecnici) quindi con estrazione ad uno strato superiore: quello funzionale. Ovviamente lo sviluppo sarà mirato a far diventare verdi  i test di ogni funzionalità o "feature".

Essendo ad uno strato funzionale si vede già uno dei vantaggi. I casi di test non sono altro che i requisiti funzionali del software. Quindi, opportunamente scritti (vedremo a vanti come), si ha un passaggio in meno nel ciclo di produzione.

Veniamo al pratico. Ci sono diversi framework che ci vengono in aiuto per sfruttare tale metodologia, fra i quali JBehave e Cucumber.

Nella mia guida userò Cucumber ma solo perchè ho avuto modo di conoscerlo meglio.

Bene il progetto di esempio (che trovate su github consiglio di visionarlo per capire la struttura dei packege ecc che qui non descrivo) è un EJB versione 3.1 quindi basato su architettura ee6. Scopo dell'applicativo è quello di fornire una funzionalità di saluti. Quando richiamato restituisce un messaggio standard se non passo parametri o un messaggio che saluta la persona passando il suo name come parametro.  Abbiamo quindi i requisiti funzionali o casi di test del BDD vediamo di fare un riepilogo:

Funzione saluto:

  Comportamento quando non viene passato il nome:
    Dato il nome vuoto
    quando chiamo la funzionalità
    mi aspetto il messaggio "Ciao, benvenuto!"

  Comportamento  quando viene passato il nome
    Dato il nome "Selen"
    quando chiamo la funzionalità
    mi aspetto il messaggio "Ciao Selen, benvenuta!"

Per comodità do per scontato che avete già creato un progetto maven dell'ejb 3.1 che supporta il CDI (file beans.xml nella meta-inf del progetto). Ecco le dependency necessarie nel pom:





Ora che abbiamo il progetto base  scriviamo i casi di test con Cucumber (e non prima il software).
Cucumber è una libreria (sviluppata inizialmente per ruby ma ha il suo porting nei diversi linguaggi fra i quali java) che come dicevo prima ci viene in aiuto nei casi di test per quanto riguarda il BDD. Fa da motore. Per informazioni dettagliate vi consiglio di documentarvi.

In breve il suo modo di operare è quello di leggere un file di configurazione che contiene i diversi comportamenti che può assumere una funzionalità. Viene chiamato file "feature" è ha tale parola come estensione. Al suo interno troviamo i possibili scenari (quindi i test da superare) descritti in linguaggio naturale (ossia quello che parliamo tutti i giorni). Dato che è in linguaggio naturale Cucumber supporta diverse lingue fra le quali l'italiano. La sintassi utilizzata nello scrivere il file è quella Gerkins.

Ogni scenario ha diversi step il compito dello sviluppatore sarà quello di implementare i singoli step. Gli step vengono eseguiti in sequenza come descritti nel file feature. Ogni step ha una sua annotation utilizzata da cucumber per mapparli e eseguirli.

Ecco come è fatto il nostro file feature



è in inglese ma quando programmo mi viene più "naturale" scrivere in inglese chissà perchè?

Be come vedete anche se in inglese è simile ai nostri requisiti funzionali ed è in linguaggio naturale (quindi può essere redatto anche da uno che sa poco o nulla di programmazione, un modo per rendere partecipi allo sviluppo le figure funzionali).

Il file lo chiamiamo hello.feature e lo posizioniamo nello stesso package della classe degli step

Nello scrivere il file feature ci viene in aiuto fighissimo plugin per eclipse chiamato appunto Natural sviluppato e mantenuto da un mio carissimo amico e al quale ho dato un piccolissimo se non insignificante aiuto nel progetto. Nel suo blog rlogiacco.wordpress.com troverete molti spunti sulla programmazione java in generale e specialmente sui temi trattati in questo mio post.

Preperiamo gli step in una classe java che chiamiamo HelloStepdefs:





come vedete i metodi (step) hanno delle annotation di cucumeber che rispecchiano le descrizioni nel file feature. Ci stanno pure in italiano. Come parametro le annotation pigliano una regex utilizzata da cucmber per mappare feature e step.


Siamo nella fase Red. Se ipoteticamente (mi manca da spiegare come far interagire cucumber con l'ejb: io ho simulato la cosa) eseguissimo  il test da maven sarebbe rosso.








come vedete abbiamo otto test uno per ogni riga del file feature. Nei quali i due scenari sono falliti poichè uno dei suoi step è fallito. Infatti i Given e il When sono andati a buon fine: se guardate il codice non producono asserzioni false. Ma il Then message  invece sì, poichè ci si aspettava in entrambi gli scenari un messaggio invece abbiamo un puntatore a null.

Altra cosa che noterete è che ci stanno 8 run di cui 2 gli scenari, quindi rimangono 6  che sono gli step di ogni scenario, ma abbiamo definito nella classe java solo 4 step come mai?
Ovviamente il When e il Then hanno la stessa regular expression valida per entrambi gli scenari quindi cucumber ha mappato lo stesso step nei due comportamenti.

Facciamo diventare il semaforo verde. Per fare ciò implementiamo un session bean di nome HelloBean


quindi se il paremetro è null ritorniamo quello di default altrimenti quello con il parametro passato.

ora non ci rimane che fare il lookup dell'EJB nella classe degli steps tramite l'annotation EJB:



quindi implementare il metodo When chiamando la funzione get message dell'ejb passando il parametro null o con valore stringa Selen a seconda del Given eseguito deciso dallo scenario descritto nel file feature.

Ora bisogna lanciare il test maven ma non viene eseguito nulla poichè l'entry point del test qual'è? E  come faccio a lanciare l' EJB se non l'ho dispiegato in un EJB container? E sopratutto perchè devo fare il  deploy dell'ejb in un application server solo per testarlo?

No problem ci vengono in aiuto Cucumber e OpenEJB. OpenEJB e un ejb container embeddable sviluppato da apache e parte del pacchetto TomEE sempre di apache (documentatevi per le sue configurazioni). Sfrutteremo il suo motore per tirare su l'EJB. Anzi lo farà cucmber per noi. Basta scrivere l'entry point del test  richiamando la object factory per OpenEJB  offerta da cucumber. Implementiamo quindi la classe HelloObjectFactoryTest:


L'istanza della classe OpenEJBObjectFatory di Cucumber starterà OpenEJB il quale dispiegherà l'EJB (ovviamente tutto deve stare nello stesso classloader) per poi fare quindi il lookup del context di OpenEJB. Con l'istruzione factory.addClass() diciamo alla factory di cucumber quali sono le classi step da eseguire. factory.start() serve a startare l'ejb e fare il lookup del context. Quindi poi con i getInstance dei vare step (nel nostro caso uno) cucmber effettua l'injection dello step nel context dell'ejb (in modo da funzionare un eventuale cdi: vedremo più avanti nel refactor) e ne esegue gli step per i tests.

Ora bisogna scrivere una classe che farà per così dire da main e che lancerà i test con il runner di Cucumber e non quello di JUnit. Classe che puo fare anche da configuratore per cucmber. Ecco la classe:



La classe ha il corpo vuoto poichè a noi serve solo per dichiarare l'annotation RunWith(Cucumber.class) ed eventualmente per passare dei parametri o options a cucumber: ad esempio dove si trova il file feature se è diverso dal package dove sta lo step oppure il formato da utilizzare per generare i reports dei test ecc. (ma non nel nostro caso).

Bene date un mvn clean test o lanciate con l'apposito comando del vostro IDE preferito. Ecco il risultato:




Semaforo Green!!

Ora facciamo il refactor: vediamo un po.... mmmm bene ci sono: facciamo in modo che il messaggio di default (in caso di mancanza di parametro) venga prodotto da una classe di risorse comuni ai vari session bean dell'ejb sfruttanto il CDI, implementiamo la classe Resource.java:





come vedete abbiamo usato l'annotation @Produces del CDI (documentatevi mi raccomando e una delle novita java più interessanti e utili). Modifichiamo l'HelloBean in modo da fare l'inject di tale risorsa e sfruttarla:



quindi rilanciamo il test e dovremmo avere di nuovo il semaforo verde. Con la differenza che abbiamo sfruttato il CDI.

Spero di essere stato utile bye!

Nessun commento:

Posta un commento