Da TDD a BDD con Symfony2

Qualche tempo fa, insieme a Riccardo, ho lavorato ad un piccolo progetto extra. Dato che il progetto era semplice ed entrambi eravamo in vena, abbiamo deciso fare un po’ di BDD per vedere come andava.
Nel mondo PHP, BDD si traduce in Behat a PHPSpec.

Come al solito lavorare su un vero progetto, anche se semplice, è un’altra storia rispetto a leggere la documentazione e ci siamo trovati ad affrontare tutta una serie di questioni. Una buona parte legate alle nostre vecchie abitudini dal mondo Xunit e altre semplicemente più legate al nuovo paradigma.

Lo scopo di questo post è di raccontare la transizione da TDD a BDD (e anche di condividere qualche snippet di codice).

Come prima cosa credo sia importante sottolineare che passare da TDD a BDD non è tanto una questione di librerie e di strumenti, quanto piuttosto di cambiare atteggiamento mentale. È importante mettere a fuoco che lo scopo è descrivere e non testare. È la comunicazione a farla da padrona. Può sembrare una banalità, ma più che testare il funzionamento del tuo codice il cuore di tutta la faccenda è descriverne il comportamento. Potresti tranquillamente fare BDD con PHPUnit invece che Behat o PHPSpec, tanto è una questione di approccio e non di strumento.

Detto questo ovviamente i tool contano, e devo ammettere che sia Behat che PHPSpec danno una gran mano nell’approcciare il problema dal giusto angolo.

I componenti

Il primo passo è realizzare con quali librerie avrai a che fare:

  • Behat è il pezzo forte: il framework di test. Ti permette di scrivere descrivere la tua applicazione in linguaggio gherkin (una sintassi praticamente identica alla lingua parlata).
  • Mink: una libreria per scrivere test di accettazione di applicazioni web
  • MinkExtension: la colla tra Behat e Mink. L’estensione configura e rende disponibile Mink all’interno delle classi Context di Behat
  • Symfony2Extension: permette di accedere ad un kernel di Symfony e mette a disposizione una sessione per Mink

Per mettere tutti i pezzi insieme ti basterà seguire la documentazione ufficiale, non c’è nessun bisogno di ripetere tutti gli step qui 😉

Questo post si riferisce alla versione 2.x di Behat. Recentemente è stata rilasciata la versione 3.x che introduce una serie di aggiornamenti radicali all’architettura del framework. Molte delle estensioni sono già state aggiornate, ma lo stesso non si può dire della documentazione. Personalmente non ho ancora provato la nuova versione.

Organizzazione dei test

Symfony2Extension offre la possibilità di avere file di “features” separati per ogni bundle o di tenerli tutti raggruppati a livello di progetto. Personalmente preferisco tenere tutte le features in una directory unica: che una funzionalità faccia parte di un bundle piuttosto che di un altro mi pare un dettaglio implementativo che non voglio esporre a questo livello. La mia esperienza si basa su un progetto relativamente semplice, però non escludo la possibilità di cambiare idea sulla questione in futuro 😉

Ad ogni modo, se anche tu vuoi tenere i tuoi file di feature tutti insieme, devi definirlo nel file di configurazione behat.yml:

Una volta iniziato a descrivere la tua applicazione nei file di features puoi anche iniziare ad implementare i relativi step nelle tue classi *Context.

Questa è una delle parti difficili
Le classi Context crescono molto in fretta (l’organizzazione degli step infatti è stata completamente rivista nella 3.x), mentre descrivi la tua applicazione continui ad aggiungere step dopo step, ma devi anche preoccuparti di mantenere questi file leggibili e gestibili.

Questo vuol dire che, probabilmente, dovrai definire dei macro steps e dovrai organizzarli in sub context per mantenere un po’ di ordine e non perdere la bussola.

Dovrai anche abituarti a saltare spesso dalla definizione dello step (gherkin) alla sua implementazione (php) e all’inizio la cosa può creare un po’ di confusione.

Questo aspetto purtroppo non si può imparare dalla documentazione, ma bisogna farsi un po’ di esperienza sul campo prima di capire bene come organizzare codice e features. Durante lo sviluppo del nostro piccolo progetto ci siamo trovati a fare refactoring dei nostri steps più di una volta, anche con una suite molto piccola. La cosa buona è che ogni volta che sposti un pezzo hai la sensazione di conoscere il tuo dominio un po’ meglio di prima. Tutte le volte che uno step sembrava fuori posto o una feature era difficile da descrivere ci siamo resi conto che il problema non era l’implementazione in se, quanto invece un po’ di conoscenza del dominio mancante e ogni refactoring ci ha dato un po’ di consapevolezza in più.

Definire una suite di test

Subito dopo l’installazione abbiamo iniziato a descrivere i nostri scenari. Non una sola funzionalità, ma tutte quelle note. Molte delle definizioni erano molto vaghe e generiche a questo punto, ma stenderle tutte insieme ci ha permesso di decidere quale era quella fondamentale, dalla quale era giusto partire, rimandando ogni decisione implementativa relativa alle altre fino al momento dell’effettiva implementazione.

Francamente era la prima volta che definivo un set completo di test per la mia applicazione, prima della prima riga di codice. Solitamente mi limitavo ad un test, o comunque ad un set più ristretto. Poter descrivere, in un linguaggio naturale, tutte le nostre funzionalità (anche se a livello macro, non nel dettaglio) ci ha agevolato nella stesura di un ventaglio molto ampio di scenari e ci ha dato modo di mettere a fuoco la visione d’insieme.

Credo che questo sia uno dei vantaggi più forti del BDD: ti spinge a cambiare il modo in cui pensi la tua applicazione.
 

Ovviamene adesso posso applicare lo stesso identico approccio anche quando uso PHPUnit (vedi, i tool non sono fondamentali) ma è una cosa che, prima di questo esercizio, non avevo mai messo a fuoco.

Eseguire la suite

Da subito ci siamo resi conto che non volevamo che tutti i test venissero eseguiti ogni volta. Avevamo la necessità di distinguere la feature in lavorazione da quelle ancora da implementare e da quelle che potevamo considerare complete.

Behat prevede il tagging, che faceva proprio al caso nostro.

Abbiamo iniziato applicando il tag “tbd” a tutti gli scenari, il tag “wip” a quello attualmente in lavorazione. In questo modo definendo la configurazione:

eseguendo bin/behat venivano eseguiti tutti gli scenari tranne quelli con il tag @tbd, mentre invece bin/behat –profile wip permetteva di eseguire solo lo scenario attualmente in lavorazione.

Una configurazione semplice, ma nel nostro caso, funzionale. Avere profili differenti è un po’ come avere suites separate, ed escludendo tutti gli scenari con il tag tbd dal profilo di default ti permette di eseguire tutto l’eseguibile senza le (inutili in questo caso) segnalazioni per gli step non ancora definiti.

Lavorare con il database

Fare testing funzionale vuol dire avere a che fare con database e dati. La regola di base è sempre la stessa: i test devono essere isolati e quindi ognuno deve partire da uno stato noto e indipendente dall’esecuzione degli altri.

La grossa differenza tra Behat e il buon vecchio PHPUnit è che non hai un solo metodo setup() ma tanti step “Given”, ad esempio:

Behat espone una serie di agganci che puoi utilizzare per eseguire operazioni come cancellare e ricreare il database. Noi abbiamo usato @beforeScenario con un’implementazione leggermente differente da quella usata in LiipFunctionalTestbundle:

Questa implementazione, per la verità, non è molto ottimizzata dal punto di vista delle performance, ma era sufficiente per il nostro progetto che aveva un database veramente piccolo e veloce da ricreare. Probabilmente per casi d’uso più complessi avrai bisogno di un modo più efficiente di cancellare e ricreare il DB (caching del file come fa LiipFunctionalTestBundle?).

Conclusioni

Penso che continuerò ad esplorare BDD. Gli strumenti sono abbastanza maturi e affidabili, e tutto l’ecosistema sembra pronto per essere adottato anche in contesti complessi. Behat e Mink sono veramente due belle librerie e PHPSpec (di cui forse parlerò in un prossimo post) è altrettanto solido.

Al momento non ho ancora identifica delle “best practices”, non sempre eravamo proprio sicuri di quello che stavamo facendo e ci sono un sacco di vecchie abitudini da abbandonare, ma la sensazione generale è stata veramente molto molto buona.

Traduzione dal blog di Francesco Tassi A nomadic web developer