BDD sau behaviour-driven development (dezvoltare bazată pe comportament), este o metodă de dezvoltare software bazată pe TDD (test-driven development sau dezvoltare bazată pe testare). Se concentrează în principal pe gestionarea procesului de dezvoltare software atât din perspectiva business cât și cea tehnică. BDD încearcă să elimine ceea ce se numește "costul traducerii" - ceea ce spune partea de business și ceea ce înțelege persoana tehnică. Cu BDD, acest lucru ar trebui să fie mult mai clar. BDD este implementat printr-un DSL (domain-specific language) simplu, folosind propoziții clare și concise în limba engleză care exprimă comportamentul și rezultatul așteptat. Inițiatorul BDD este Dan North în 2006.
Exemplu:
Given I am logged in
When I press the Logout button
Cucumber oferă un limbaj ușor de folosit numit Gherkin. Gherkin folosește o sintaxă simplă foarte asemănătoare cu limba engleză, cu posibilitatea de a utiliza variabile. Fiecare afirmație din Gherkin este precedată de cuvintele-cheie Given-When-Then. Acestea sunt acolo mai ales din punct de vedere gramatical, indicând totodată modul în care urmează să fie utilizat acest pas:
Given (Dat fiind că): Setează pre-condițiile în care trebuie să se afle aplicația sau utilizatorul, înainte ca restul scenariului să fie executat - de exemplu, "Dat fiind că sunt conectat" - asigură că utilizatorul este conectat, navigând prin aplicație pentru a-l conecta;
When (Când): Se referă la anumite acțiuni pe care utilizatorul le efectuează asupra aplicației (ex: "Când apăs butonul Logout");
Există, de asemenea, And (si) si But(dar); acestea sunt doar pentru a defini precondiții / acțiuni / așteptări mai complexe. Toate aceste cuvinte cheie sunt, din punct de vedere tehnic, interschimbabile. Odată ce un pas este definit cu unul dintre acestea, poate fi refolosit cu oricare altul și va face același lucru. O altă caracteristică foarte utilă a limbajului Gherkin este faptul că suportă variabilele.
De exemplu:
Când mă conectez cu utilizatorul
"johnsmith@someemail.com"
Și introduc parola "secretPassword";
Numele de utilizator și parola sunt variabile aici. Când reutilizăm pasul, putem trimite în orice altă valoare dorim.
Toate cele de mai sus sunt doar fraze în limba engleză, nu fac nimic de la sine. Toate trebuie să fie implementate folosind un limbaj de programare și, probabil, anumite librării, în funcție de ceea ce doriți să faceți.
După cum am văzut mai sus, ideea principală a BDD este să aibă un limbaj comun între partea business și echipa tehnică. Combinând acest lucru cu limbajul Cucumber și Gherkin, unde avem o condiție prealabilă, o acțiune și apoi o verificare (o așteptare), putem vedea cu ușurință că acest tipar este foarte potrivit pentru testarea aplicațiilor: aplicația este într-o anumită stare, se acționează asupra aplicației și apoi se verifică rezultatul - așa arată, în general, acceptance testing. Putem merge până la a spune că această metodă de a analiza o aplicație ar putea servi ca o documentație, un document de design la nivel înalt.
Am menționat că toate aceste propoziții în limba engleză, sau pași așa cum sunt numiți în BDD, au cod în spatele lor. Aceasta deschide posibilitatea ca acești pași să fie executați de o mașină care ar trece prin aplicație, o va aduce într-o stare cunoscută, va executa anumite acțiuni asupra aplicației și va verifica starea în care se află, altfel spus: testare automată.
Pentru a testa automat aplicația, avem nevoie de câteva librării care pot interacționa cu dispozitivele (pentru testarea automată pe dispozitive mobile) sau cu browserul (testarea automată web). Codul din spatele pașilor este definit prin folosirea oricărui limbaj de programare dorit (am decis Java și Ruby) și Appium și Calabash pentru testare mobilă și Selenium WebDriver pentru testare web.
Pentru a reduce timpul de mentenanță și lucrurile să fie cât mai scalabile, am ales o arhitectură stratificată, unde la nivelul cel mai de jos avem funcții care integrează funcționalitatea librăriilor alese (și alte funcții specifice aplicației). La nivelul următor, avem definită funcționalitate comună pentru aplicația testată, iar nivelul cel mai de sus reprezintă implementarea pașilor propriu-ziși. Această arhitectură separată pe straturi permite o mentenanță mai ușoară, urmând să intervenim exact la stratul care este nevoie cu un impact minim asupra celorlalte nivele. O problemă pe care am întâlnit-o, unde stilul nostru de arhitectură a ajutat, a fost trecerea de la o librărie la alta, mai specific de la Calabash la Appium, când Calabash nu a mai fost dezvoltat. A fost necesară doar înlocuirea stratului de la bază din Calabash în Appium și restul a funcționat de la sine.
Am încercat să păstrăm pașii cât mai modulari și individuali posibil, documentându-i cu trei mențiuni majore: precondiția (în ce stare trebuie să fie aplicația pentru a folosi acel pas), acțiunea pasului în sine și starea aplicației în care va rămâne aplicația după execuția pasului.
Am încercat să evităm pașii prea generali, care ar necesita ca utilizatorii mai puțin tehnici să aibă cunoștințe despre cum se obține un selector, cum ar fi selectorii CSS sau, în cel mai rău caz, selectorii XPath. Un alt lucru luat în considerare este numărul de parametri utilizați. Nu putem avea prea mulți parametri într-un pas, deoarece într-un anumit moment, pasul în sine ar deveni prea instabil, mentenanța mai greoaie și dificil pentru ca utilizatorul să-l înțeleagă. Cu toate acestea, nu putem avea nici prea puțini parametri, pentru că ar însemna că un pas este foarte rigid și că vom ajunge la un număr prea mare de pași. Deci, am decis că maximum trei parametri pe pas este numărul magic.
Chiar și așa, am ajuns la un număr mare de pași, având în vedere faptul că aplicația testată este destul de complexă și că există trei clienți diferiți pentru aceeași aplicație, fiecare cu propriile particularități (web, Android și iOS). Aveam nevoie de o modalitate de a gestiona toți acești pași într-o manieră simplă și ușor de folosit, așa că am creat o bază de date care să îi conțină, adăugând diverse detalii pentru fiecare pas (clientul de care aparține pasul, documentația etc.) și o aplicație pentru a interacționa cu acea bază de date. O aplicație care în timp a devenit mult mai complexă, s-a transformat într-un instrument prin intermediul căruia utilizatorii obțin toți pașii din baza de date, creează teste noi cu acești pași, le salvează înapoi în baza de date, realizează suite de testare cu testele create anterior, le salvează și pe acestea în baza de date. Dar cea mai importanta funcționalitate a aplicației, este că utilizatorii pot executa aceste suite pe medii personalizate (versiune de aplicație, serverul de test, versiune de browser etc.).
Nu putem susține că am urmat în întregime paradigmele BDD, dar am folosit ceea ce am considerat ca se potrivește cel mai bine cu nevoile noastre. Mai clar, testare automată și posibilitatea ca oricine să poată crea noi teste automate.
Paradigma BDD este mult mai complexă decât felul în care am utilizat-o noi, dar am valorificat-o în modul în care ne-a avantajat cel mai mult. Cea mai importantă caracteristică a BDD este sintaxa ei asemănătoare englezei, pe care oricine o poate înțelege. Oamenii mai puțin tehnici au apreciat-o pentru că își puteau scrie testele în limba engleză utilizând dicționarul nostru de pași, baza de date și aplicația pe care am dezvoltat-o. De fiecare dată când au fost adăugate funcționalități noi în aplicație, am făcut pași noi pentru acestea, reușind de cele mai multe ori să refolosim pașii vechi cu foarte puțin cod nou. Același lucru a fost și în cazul oricăror solicitări venite din partea unor persoane non-tehnice: "Aș vrea să fac asta. Nu văd niciun pas care să se ocupe de asta. L-ați putea crea? "
Unul dintre lucrurile pe care frameworkul nostru a vrut să le facă este să aibă abilitatea de a scrie teste care să fie agnostice de platformă. În cazul nostru, cei trei clienți - web, android și iOS. Am vrut să oferim utilizatorilor posibilitatea de a scrie un test și de a le rula pe toți cei trei clienți, dacă acel scenariu testează o funcționalitate comună celor trei clienți.
Să presupunem că facem niște teste negative privind autentificarea în aplicație. Toți trei clienții au funcția de autentificare, am scris pașii astfel încât să fie utilizabili pe orice platformă. Desigur, acest lucru nu este atât de ușor de făcut în spatele cortinei, când trebuia să implementăm acești pași, iOS utilizează o librărie, Android alta, iar platforma web, o a treia librărie pentru a interacționa cu fiecare client în parte. În plus, au fost scrise în diferite limbaje de programare - Java și Ruby. Deci, nivelul de integrare dintre acești trei clienți era la nivelul de sus, unde toți trei aveau un singur lucru în comun - limbajul Gherkin. În Gherkin, totul arată la fel, indiferent de limbajul pe care îl avem în spatele lui sau de ce librării folosim. "Given I am logged in", de exemplu, nu face nici o mențiune despre un limbaj de programare și nici o cerință specifică a librăriei folosite, ci doar loghează utilizatorul în aplicație.
Ușor de transformat testele manuale în teste automate.
Codul poate fi refolosit deoarece implementarea pentru fiecare pas nu se modifică. Codul de devine foarte modular.
Scenariile de testare devin mai ușor și mai rapid de scris și de automatizat, pe măsură ce sunt adăugați mai mulți pași.
Acest lucru este greu de estimat și variază în funcție de fiecare proiect. Ceea ce putem spune este ca, indiferent de cât de mult ține, acest timp va fi recuperat mai târziu când vine vorba de mentenanță și scalabilitate. În cazul nostru, am avut nevoie de un an și jumătate pentru a automatiza cam tot ce avea sens în proiectul nostru, dar aici ar trebui menționat că am avut de a face cu un proiect extrem de complex.
De-a lungul dezvoltării proiectului am observat câteva lucruri care ne-ar putea fi utile pe viitor. Unul dintre acestea se referă la testarea vizuală: facem o poză la un anumit moment în aplicație și o comparăm cu o altă poză de referință, despre care știm sigur că arată exact cum ar trebui să arate aplicația în acel punct.
de Ovidiu Mățan
de Ovidiu Mățan