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:
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:
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:
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:
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:
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:
Design-ul incremental combate problemele enunțate anterior astfel:
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:
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.
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.