Behavior Driven Development (BDD) a fost introdus prima dată de către Dan North ca o modalitate de a explica Test Driven Development (TDD) unor clienți care intenționau să utilizeze această metodologie în dezvoltarea proiectului (vezi [1] pentru mai multe detalii despre cum a apărut BDD). BDD reprezintă un ansamblu de practici de colaborare gândite pentru a facilita dezvoltarea unor aplicații orientate către cerințele utilizatorilor (build the right software, în contrast cu obiectivul TDD, al cărui motto ar putea fi build the software right).
BDD propune o abordare ce are ca scop reducerea erorilor de înțelegere ale unei specificații în cadrul unei echipe, adesea de neevitat atunci când o informație este transferată de la un stadiu la altul în procesul de dezvoltare software (de la client către business analyst, apoi la programatori și testeri).
BDD nu reprezintă o metodologie de dezvoltare software de sine stătătoare (nu încearcă să abordeze toate zonele acestui domeniu vast), astfel că va trebui să fie utilizată în corelație cu alte metodologii, precum Agile, Test Driven Development sau Domain Driven Development.
*"BDD practitioners use conversations around concrete examples of system behaviour to help understand how features will provide value to the business. BDD encourages business analysts, software developers, and testers to collaborate more closely by enabling them to express requirements in a more testable way, in a form that both the development team and business stakeholders can easily understand. BDD tools can help turn these requirements into automated tests that help guide the developer, verify the feature, and document what the application does." - BDD in Action
Am selectat trei beneficii generate de aplicarea practicilor BDD, deoarece cred că acestea sunt cele mai importante puncte ce ar trebui luate în considerare atunci când se pune problema adoptării BDD în cadrul unui proiect:
Toți membrii echipei au aceeași înțelegere a modului în care ar trebui să funcționeze aplicația dezvoltată.
Toți membrii echipei sunt implicați, încă din fazele incipiente, în dezvoltarea unei noi funcționalități a aplicației. Atât analiștii business (business analysts), cât și programatorii sau testerii colaborează cu utilizatorii finali sau cu clientul pentru a înțelege care e scopul unei funcționalități ce trebuie dezvoltată. Aceștia încearcă să descopere împreună cum anume îi va ajuta această nouă funcționalitate pe utilizatori. Rezultatul acestei colaborări este un set de exemple definite într-un format semi-structurat. Aceste exemple vor forma criteriile de acceptanță ale funcționalității.
Cea mai mare parte a documentației care se găsește în proiecte nu este actualizată, deoarece echipa se concentrează mai degrabă pe adăugarea de noi funcționalități decât pe actualizarea documentației pentru a reflecta ultima versiune a codului. Ar fi nevoie de o disciplină de fier din partea membrilor echipei pentru a ține evidența tuturor modificărilor în cod și, chiar dacă acest lucru ar fi posibil, este aproape imposibil să ne asigurăm că specificațiile nu reflectă ceea ce se presupune că face aplicația, mai degrabă decât cum funcționează efectiv. Pentru a rezolva această problemă, BDD propune traducerea exemplelor definite de către echipă în teste automate (sau specificații automate, așa cum adesea sunt denumite) folosind unele framework-uri specializate, cum ar fi JBehave sau Cucumber. Aceste teste furnizează o documentație a celei mai recente versiuni a codului. În acest fel nu există nici o posibilitate să avem o versiune neactualizată a specificației: atunci când fie exemplele, fie codul se modifică, testele eșuează, indicând că o anumită parte (documentația sau codul) trebuie actualizată.
Pornind de la faptul că exemplele ce descriu o funcționalitate sunt automatizate, prin simpla execuție a testelor poate fi demonstrat că aplicația se comportă exact așa cum s-ar aștepta utilizatorii să o facă. Rezultatele execuției testelor pot fi vizualizate în formatul unui raport (de exemplu, multe tooluri au capacitatea de a genera rapoarte în format HTML). Aceste rapoarte furnizează informații utile privind funcționalitățile deja implementate și a funcționalitățile aflate în curs de dezvoltare. De asemenea, exemplele pot fi foarte ușor analizate de membrii fără cunoștințe tehnice ai proiectului.
În această secțiune a articolului voi încerca să ofer o descriere succintă a procesului utilizat prin aplicarea BDD prin urmărirea unui exemplu concret. Acesta nu va fi un exemplu complet, astfel că dacă aveți nevoie de explicații mai detaliate asupra acestui subiect, puteți utiliza alte tutoriale de BDD.
La un nivel general, BDD constă din următorii pași:
Primul pas este realizat de către client (sau de către persoanele care vor beneficia de aplicație), pentru că acesta știe cel mai bine cum vor fi ajutați utilizatorii de această aplicație. La acest stadiu, persoanele implicate în proiect încearcă să răspundă la întrebările: Ce se încearcă să se îndeplinească prin implementarea acestei aplicații? sau De ce este nevoie ca aplicația să fie realizată?. Pornind de la răspunsurile găsite, la un stadiu ulterior, vor putea fi găsite răspunsuri pentru întrebarea Cum pot fi îndeplinite așteptările clientului?
Un exemplu de astfel de scop pentru o aplicație de cumpărături online ar putea fi exprimat astfel:
Provide the users the easiest way possible for reaching the products they need.
Pentru a transcrie mai ușor în formatul BDD standard, am optat pentru exprimarea exemplelor în limba engleză. Chiar dacă există și variante în limba română pentru exprimarea acestor exemple, am considerat că sună mai natural în limba engleză.
Pentru fiecare scop de business definit la pasul precedent vor fi identificate funcționalitățile necesare pentru a îndeplini aceste obiective. Funcționalitățile vor fi identificate ca rezultat al colaborării dintre membrii echipei, atât cei având cunoștințe tehnice cât și persoanele non-tehnice implicate în proiect. O funcționalitate ce îndeplinește scopul propus ar putea fi următoarea:
In order to find in an easy way the products I need
As a user
I want to be able to search the products by category
Practicanții BDD recomandă ca prima propoziție a descrierii unei funcționalități să fie chiar scopul de business, pentru a clarifica ce anume trebuie realizat. Nici acest pas, nici pasul precedent nu sunt acțiuni care se realizează o singură dată pe parcursul dezvoltării unui proiect. Din multe motive, este posibil ca scopurile de business să se schimbe sau în timpul implementării să fie identificate modalități mai bune pentru a îndeplini anumite scopuri.
Pentru a înțelege mai bine ce ar trebui implementat pentru o funcționalitate, membrii echipei vor specifica exemple ale unor cazuri reale de utilizare. Aceste exemple vor forma criteriile de acceptanță ale funcționalității și vor ghida implementarea. Pentru specificarea acestor exemple va fi utilizat un format semi-formal, ca de exemplu:
| Name | Description | Category |
| USB | USB cable | Connectors |
| Mouse | Wireless Mouse | Peripheral devices |
|Keyboard |Wireless Keyboard |Peripheral devices |
|HDMI |HDMI cable | Connectors |
When a user searches all the products in the "Peripheral devices" category
Then the following products are returned:
|Name |Description |
|Mouse |Wireless Mouse |
|Keyboard |Wireless Keyboard |
Formatul Given-When-Then se numește Gherkin și oferă o modalitate simplă și expresivă de specificare a unui exemplu. De asemenea, acest format poate fi transcris foarte ușor sub forma unor teste automate.
Pentru a ilustra modul în care exemplele anterioare sunt traduse sub forma testelor automate, voi utiliza JBehave, un framework Java pentru implementarea BDD. JBehave mapează o propoziție dintr-un exemplu (sau scenariu, în terminologia JBehave) la o metodă al unei clase de test ce implementează pașii specificați prin interacțiunea cu aplicația. JBehave recunoaște mai multe cuvinte cheie, precum Given, When, Then, And sau But și poate fi integrat cu diferite tooluri de testare pentru a testa aplicația la diferite niveluri (de exemplu, se poate utiliza Selenium pentru a interacționa direct cu interfața Web a aplicației).
Drept exemplu, următoarele rânduri prezintă o parte dintr-o clasă de test care automatizează partea de When a scenariului:
List products = null;
@When("a user searches all the products in the "$categoryName"
category")
public void whenAUserSearchesAllTheProductsInACategory(
@Named("categoryName")final String categoryName){
products = productService.searchByCategory(categoryName);
}
După cum puteți observa în acest exemplu, maparea se realizează pe baza întregii propoziții. Numele categoriei a fost parametrizat (folosind caracterul $ înaintea numelui parametrului), ceea ce permite modificarea valorii dacă e nevoie sau furnizarea unui set de valori pentru care este așteptat același comportament.
Prima iterație va rezulta într-un test care eșuează, dar acesta ne poate ghida în implementarea funcționalității dorite. După execuția acestor teste, poate fi generat un raport de execuție într-un format ușor de înțeles de către toți membrii echipei. Multe echipe au automatizat publicarea acestor rapoarte sub forma unor pagini web pentru a fi ușor accesibile pentru orice persoană implicată în proiect. Rapoartele furnizează multe detalii utile, incluzând informații despre progresul înregistrat în implementarea unei funcționalități, ce funcționalități urmează să fie implementate sau dacă aplicația nu mai funcționează conform cerințelor ca urmare a ultimelor modificări ale codului.
La nivelul unui test unitar, BDD recomandă scrierea testelor sub forma unor specificații low level (în contrast cu testele de acceptanță care reprezintă high level specifications) cu scopul de a ajuta programatorii să înțeleagă mai ușor care e rolul modulului pentru care au fost scrise testele. Este recomandat, de asemenea, ca numele testelor să înceapă cu cuvântul should urmat de acțiunea așteptată.
Exemplul de mai jos ilustrează testarea metodei searchByCategory() a clasei ProductService, într-o arhitectură clasică ce respectă pattern-ul 3-Layer (Presentation - Service - Persistence). În acest exemplu se testează interacțiunea dintre nivelurile de business logic și persistence cu scopul de a găsi toate produsele dintr-o anumită categorie.
@Test
public void shouldReturnAllProductsForCategory_withValidCategoryName(){
//Given
final String categoryName = randomCategoryName();
final List products = randomListOfProducts();
BDDMockito.given(productRepositoryMock.findByCategory(categoryName ))
.willReturn(products);
//When
final List result = productService
.searchByCategory(categoryName);
//Then
MatcherAssert.assertThat(result, Matchers.equalTo(products));
}
Pentru acest test am folosit librăriile BDDMockito (o adaptare a frameworkului Mockito, care permite definirea mockurilor folosind cuvintele cheie din BDD) și Hamcrest (o librărie pentru a scrie asserturi într-un mod mai ușor de citit). Același rezultat ar putea fi atins și folosind doar JUnit sau alt framework de testare, fără a apela la alte frameworkuri suplimentare, prin separarea testelor conform formatului Given-When-Then.
După finalizarea pașilor expuși anterior funcționalitatea propusă ar trebui să fie implementată cu succes și pentru aceasta să existe teste automate care să garanteze că funcționează conform așteptărilor.
Ca de obicei, teoria nu se reflectă în mod direct în practică, întotdeauna intervenind ajustări în conformitate cu cerințele proiectului. În următoarele rânduri voi încerca să vă ajut să vă faceți o idee despre modul în care BDD se integrează în procesul de dezvoltare software la proiectului pe care lucrez.
Dezvoltarea unei noi funcționalități începe cu o descriere informală a ceea ce trebuie implementat. Această descriere e realizată de către product owner. Documentul reprezintă doar un punct de pornire, menit să ajute membrii echipei să înțeleagă ce anume trebuie implementat și, mai ales, de ce este nevoie de această funcționalitate.
Toți membrii echipei iau apoi parte la un meeting în care se discută detaliile noii funcționalități. Ca o activitate preliminară, fiecare membru al echipei citește specificația anterior menționată pentru a identifica posibile constrângeri de implementare sau zone ce necesită mai multe explicații. În timpul discuției sunt schițate câteva exemple de utilizare, astfel echipa acumulând mai multe informații despre ce ar trebui implementat și cum anume va ajuta această funcționalitate utilizatorii aplicației.
După discuție, testerii definesc alte exemple suplimentare. Scenariile sunt definite într-un fișier text ce va constitui apoi baza pentru implementarea testelor funcționale. Toți membrii echipei colaborează apoi pentru a rafina exemplele și pentru a se asigura că acestea surprind cele mai importante cazuri de utilizare pentru funcționalitatea curentă. Colaborarea se realizează prin deschiderea unui Pull Request în GitHub, fiecare dintre persoanele implicate în dezvoltare putând adăuga comentarii la scenariile adăugate.
Când toată lumea este mulțumită cu forma scenariilor identificate, implementarea poate începe. Pe măsură ce implementarea evoluează, unele dintre exemple sunt automatizate. La sfârșitul primei iterații a implementării doar unul sau două scenarii principale sunt automatizate. În acest moment, noua funcționalitate este disponibilă doar pentru un grup restrâns de utilizatori (canary release) și, dacă aceștia sunt mulțumiți cu implementarea actuală, restul exemplelor sunt automatizate și implementarea funcționalității cerute poate fi considerată finalizată. În mod contrar, pot fi realizate modificări ale codului și ale scenariilor de test.
Am putut vedea în acest articol că BDD oferă un ansamblu de practici pentru furnizarea software-ului potrivit (right software), acest rezultat atingându-se ca urmare a colaborării întregii echipe. Principalele beneficii derivate din aplicarea BDD în cadrul unui proiect includ: toți membrii echipei au aceeași înțelegere asupra funcționalității proiectului; integrarea membrilor noi în echipă este mai ușoară; BDD furnizează o modalitate de a observa progresul proiectului și de a demonstra că implementarea este corectă; documentația reflectă întotdeauna starea actuală a codului.
Ulterior a fost descris, prin intermediul unui exemplu, modul în care practicile BDD pot fi aplicate pentru dezvoltarea unei noi funcționalități. Cum aceste practici sunt cel mai adesea doar puncte de referință, am oferit o scurtă descriere a modului în care BDD poate fi aplicat în cadrul unui proiect real.
Chiar dacă vi se pare interesantă această abordare și veți dori să aplicați BDD în cadrul proiectului pe care lucrați, consider că este necesar să se realizeze o analiză amănunțită înainte de a lua o decizie. În primul rând, vor trebui identificate problemele ce trebuie rezolvate și, dacă se încadrează în cele prezentate în acest articol, atunci BDD ar putea fi o opțiune. Dar acest lucru nu este de ajuns. Pentru a putea utiliza BDD este nevoie să fie îndeplinite anumite cerințe. De exemplu, dacă proiectul nu permite o colaborare strânsă cu clientul, nu veți putea beneficia de avantajele oferite de BDD. De asemenea, dacă cerințele sunt destul de simple și toți membrii echipei înțeleg ce trebuie implementat, atunci definirea și implementarea scenariilor ar fi o risipă de timp. Ca o concluzie, așa cum se întâmplă în cele mai multe cazuri, trebuie alese uneltele potrivite pentru îndeplinirea scopurilor propuse.