ABONAMENTE VIDEO TESTE REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 15
Abonament PDF

Test Driven Development și design incremental

Alexandru Bolboacă
Agile Coach and Trainer, with a focus on technical practices
@Mozaic Works
DIVERSE


După cum afirmam în articolele anterioare despre software craftsmanship, Test Driven Development este una dintre practicile considerate de bază pentru artizanii software. În ciuda numărului tot mai mare de articole, blog-uri, filmulețe sau cărți despre acest subiect, Test Driven Development (TDD) continuă să fie un subiect de confuzie în comunitățile de programatori. Acest articol va încerca să structureze și să clarifice subiectul și să ofere suport celor care vor să învețe mai multe despre el.

Descrierea clasică a TDD este că programatorul:

  • Scrie un singur test automat care pică (adesea numit pasul "Red");
  • Realizează cea mai mică modificare în cod pentru a face testul să treacă ( pasul "Green");
  • Refactorizează codul (pasul "Refactor").

Acest ciclu se repetă cu o frecvență mare, ajungând la maxim 5 minute pentru practicienii experimentați. Pentru începători, 15-30 min este o durată normală care scade odată cu acumularea experienței. Nu discutăm aici despre scris teste pe cod existent; în acest caz timpul necesar pentru a scrie teste este proporțional cu complexitatea codului.

Această descriere este foarte ușor de transmis, dar din păcate nu conține detalii importante pentru cei care vor să înceapă să aplice TDD, lucru care creează confuzie.

Primul lucru pe care trebuie să-l înțelegem despre TDD este că, în ciuda numelui (dezvoltare condusă de teste), nu este o metodă de testare. TDD este o metodă de a obține un design potrivit pentru problema rezolvată. Testele sunt folosite cu două scopuri:

  • avansul design-ului (soluției)
  • verificarea faptului că modificările din cod nu au afectat rezolvarea problemei de până la momentul rulării lor.

Deoarece noțiunea de software design este ambiguă, merită explicat ce înseamnă design în acest context. Design-ul nu este altceva decât "crearea de artefacte care rezolvă probleme". În cazul programării, artefactul creat este codul. Mai exact, la nivelul cel mai de jos, artefactele create sunt variabile, metode, clase și modurile de colaborare dintre obiecte (numite și "contracte").

Două lucruri sunt așadar importante pentru design-ul unei aplicații software:

  • rezolvă o problemă...
  • cât mai simplu și elegant posibil

Dacă programatorii scriu cod elegant care nu rezolvă o problemă, atunci prin definiție nu au obținut design. Cel mai rapid și elegant mod de a demonstra că problema este rezolvată este prin rularea unor teste automate care pot fi revizuite că acoperă toate aspectele problemei.

Găsirea unor rezolvări simple și elegante se izbește de câteva obstacole:

  • Neînțelegerea completă a problemei. Creierul uman are o capacitate limitată, iar problemele pe care programatorii trebuie să le rezolve cresc în complexitate. Nu ar trebui așadar să mire pe nimeni că uneori nici cel mai bun programator nu reușește să înțeleagă toate aspectele unei probleme.
  • Generalizarea pripită (precum și alte "cognitive biases" ). Adesea programatorii doresc să obțină o soluție mai generală, înainte de a avea destule cazuri particulare care o justifică. Dusă la extrem, această tendință poate crea un cod aparent bine conceput care însă este foarte greu de ținut la zi.
  • Tendința de a folosi soluțiile cunoscute. "Când ai un ciocan, vezi în jurul tău doar cuie", spune o vorbă veche. Problemele pe care le rezolvă programatorii în fiecare zi pot părea la un nivel superficial foarte asemănătoare. Realitatea este că soluțiile în programare depind foarte mult de mici detalii.
  • Schimbarea rapidă a cerințelor. Este deja un fapt cunoscut că cerințele se schimbă de la o zi la alta. O soluție care era bună ieri s-ar putea să nu se mai potrivească azi.

Realitățile de zi cu zi din viața unui programator duc la nevoia de a avea un design ușor de modificat. Cerințele se modifică, echipele se modifică, programatorii învață mai multe în fiecare zi despre produs și tehnologii. În zilele noastre, design bun este cvasi-sinonim cu design ușor de modificat. De aceea, calitățile unui design bun sunt:

  • Atât structura cât și codul sunt ușor de înțeles pentru toți programatorii implicați.
  • Majoritatea funcționalităților cerute pot fi implementate cu modificări minimale în cod. Acest lucru este posibil când clase mici și foarte specializate lucrează împreună conform unor contracte bine definite între interfețele lor.
  • Este ușor de verificat dacă modificările din cod nu au afectat implementarea existentă.

O soluție pentru aceste probleme este design-ul incremental. Design-ul incremental înseamnă crearea design-ului în timpul scrierii codului. Design-ul incremental este o alternativă la metoda clasică de a face design: înainte de a începe scrierea codului, pe hârtie sau în unelte specializate.

Pentru a face design incremental, e nevoie de următorii pași:

  • Analiza problemei și împărțirea ei în probleme mai mici. De exemplu, în cazul creării unui joc de Tetris, se poate porni de la cel mai simplu joc cu putință: o piesă cât un singur pătrat care cade într-o fântână cu înălțimea 1 și jocul se termină (alternativ, se poate considera că a umplut o linie care trebuie eliminată, doar că introduce regula eliminării liniei care poate fi ușor adăugată mai târziu).
  • Identificarea unor exemple concrete (valori de intrare și ieșire așteptate). De exemplu, după ce jocul începe, piesa apare pe tablă într-o anumită poziție.
  • Implementarea câte unui exemplu în cel mai simplu mod posibil.

Design-ul incremental combate problemele enunțate anterior astfel:

  • Prin definirea de exemple, problema devine mai clară. Mai mult, exemplele pot fi discutate mult mai ușor cu persoane non-tehnice (clienții) decât codul.
  • Lista de exemple este completată pe măsură ce noi comportamente sunt identificate în timpul dezvoltării. Prin iterare, șansele de a rata părți din problemă scad.
  • Simplificarea problemei permite diminuarea complexității astfel încât creierul să o poată gestiona.
  • Implementarea celei mai simple soluții pentru fiecare exemplu permite evitarea generalizării pripite.
  • Soluția este tot timpul simplă (atât cât permite problema), ceea ce ușurează modificarea ei în cazul schimbării cerințelor.

Test Driven Development este cea mai bună metodă cunoscută de a face design incremental. Testele codează exemplele de utilizare a soluției și pot fi folosite pentru verificarea continuă a ei. Implementarea celei mai simple soluții la fiecare moment ajută la evitarea generalizărilor pripite. Refactorizarea duce la simplificarea soluției.

Deoarece dezvoltarea folosind TDD pornește de la exemple iar codul scris la fiecare pas este cât mai simplu și fără generalizări, pasul de refactorizare implică mai ales identificarea și reducerea similarităților din cod (numite uneori "duplicare", deoarece două bucăți de cod fac același lucru în moduri diferite). Similaritățile pot fi eliminate doar prin generalizare, care se traduce în cod prin abstracții. (Nu este vorba doar de clase abstracte, ci și de clase care servesc pentru un scop mai larg decât au fost inițial concepute).

Prin introducerea abstracțiilor, programatorul obține un design flexibil, perfect adaptat la problema curentă. Datorită folosirii testelor automate, programatorul poate oricând demonstra că soluția sa este perfect validă pentru lista de comportamente definită prin teste. Sună excelent, nu-i așa?

Adopția TDD nu este însă simplă. La nivel personal, câteva obstacole trebuie depășite:

  • Învățarea tehnicilor necesare pentru scrierea de teste unitare simple. Foarte importante sunt aici dublele de testare (în principal stubs și mocks).
  • Simplificarea. Aceasta este o abilitate care se dezvoltă în timp, prin exercițiu și cu feedback de la alte persoane.
  • Identificarea similarităților din cod. Anumite similarități sunt evidente, pe când altele sunt mai subtile și pot fi percepute cu antrenament.
  • Design. Similaritățile pot fi diminuate prin diverse moduri, iar unele conduc la rezultate mai bune decât altele. Cunoștințele de software design (design patterns, design principles dintre care menționăm SOLID principles și cele patru principii ale designului simplu) sunt esențiale pentru obținerea celor mai simple soluții.
  • Concentrarea mai mare pe problemă și mai redusă pe soluție. Școala îi învață pe programatori să se gândească la soluții. Designul incremental cere identificarea de exemple și simplificarea problemei înainte de a scrie prima linie de cod. Programatorul care încearcă TDD trebuie să își elimine tendința de a se gândi la design sau la cod în această primă fază.
  • Refactorizarea rapidă și eficientă. Refactorizarea poate lua mult timp dacă nu este pe deplin stăpânită de programatori. Exersarea tehnicilor de refactorizare cu scopul de a crește viteza este foarte importantă pentru mediile de producție.

La nivelul unei echipe, e nevoie în plus de o perioadă de armonizare a stilului de design și de scris teste. În cazul unor medii mai complexe (mult cod existent netestat, mai multe echipe care lucrează la același produs, lucrul la distanță etc.), adopția trebuie tratată cu mare grijă pentru a evita problemele legate de productivitate. Recomandarea este în situațiile complexe să se recurgă la coaching tehnic pentru gestiunea schimbărilor.

Concluzie

Designul incremental înseamnă crearea designului pe măsură ce codul e scris. Designul incremental este o alternativă la designul făcut pe hârtie sau în unelte specializate înainte de a scrie cod. Design-ul incremental pornește de la exemple și generalizează soluția pe măsură ce apar dovezi (similarități în cod).

Acesta este și avantajul design-ului incremental: se bazează pe dovezi și nu pe intuiție (ceea ce ar putea să ni se ceară în viitor). Soluțiile generate în urma acestui proces sunt cele mai simple pentru problema data, atât cât e ea cunoscută la momentul respectiv.

TDD este cea mai bună metodă cunoscută de a face design incremental. Practicienii TDD codifică exemplele folosind teste care sunt apoi păstrate pentru a valida soluția completă. Prin identificarea și diminuarea similarităților din cod în pasul de refactorizare, design-ul este simplificat și îmbunătățit în continuu.

Scopul primar al designului incremental este de a obține design simplu și ușor de modificat cât mai rapid posibil. TDD și design incremental dau un plus de eficiență mai ales pentru rezolvarea unor probleme complet noi și care nu sunt bine stăpânite de către programatori.

Pentru adopția TDD este nevoie de exersare, singur sau într-o comunitate, folosind una din metodele descrise în articolele anterioare despre software craftsmanship: etc.

Întrebările pe acest subiect sunt binevenite pe programez.ro sau direct către autori.

LANSAREA NUMĂRULUI 73, CLUJ

Prezentări articole și
Panel: Electronic trading

Marți, 17 Iulie, ora 18:00
restaurant Fragment

Înregistrează-te

Facebook Meetup

Sponsori

  • kronsoft
  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • Crossover
  • MHP
  • BCR
  • Itiviti
  • Connatix
  • MicroFocus
  • Colors in projects