ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ 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.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects